Compare commits
14 Commits
bce746ca01
...
734b5b7ac5
Author | SHA1 | Date |
---|---|---|
May B. | 734b5b7ac5 | |
May B. | 3b4e9fd940 | |
May B. | 4066bf90b9 | |
May B. | 5ac3a0037a | |
May B. | b7669df9ed | |
May B. | bfba0544d6 | |
May B. | b98a791fc4 | |
May B. | dc7c70d871 | |
May B. | e7d34b82be | |
May B. | c584a79908 | |
May B. | bcd3475846 | |
May B. | 7a51061fce | |
May B. | 2f3335fe88 | |
May B. | 05c69190d2 |
|
@ -0,0 +1,3 @@
|
|||
[submodule "include/tracy"]
|
||||
path = include/tracy
|
||||
url = https://github.com/wolfpld/tracy.git
|
|
@ -68,7 +68,7 @@
|
|||
{
|
||||
"label": "exec callgrind",
|
||||
"type": "shell",
|
||||
"command": "valgrind --tool=callgrind --dump-instr=yes --simulate-cache=yes --fair-sched=yes ./univerxel",
|
||||
"command": "valgrind --tool=callgrind --simulate-cache=yes --fair-sched=yes ./univerxel",
|
||||
"options": {
|
||||
"cwd": "${workspaceRoot}/build"
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||
endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
|
||||
# set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=clang-analyzer-*,cppcoreguidelines-*,performance-*,readability-*,-readability-braces-around-statements,-readability-uppercase-literal-suffix")
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
|
@ -39,21 +39,27 @@ add_definitions(
|
|||
)
|
||||
|
||||
file(GLOB_RECURSE SOURCES "src/*/*.cpp")
|
||||
file(GLOB INCLUDE_SOURCES "include/imgui-1.76/*.cpp" "include/FastNoiseSIMD/*.cpp" "include/Remotery/lib/*.c")
|
||||
file(GLOB INCLUDE_SOURCES "include/imgui-1.76/*.cpp" "include/FastNoiseSIMD/*.cpp" "include/tracy/TracyClient.cpp" "include/meshoptimizer/*.cpp")
|
||||
set(INCLUDE_LIBS
|
||||
"include/imgui-1.76"
|
||||
"include/FastNoiseSIMD"
|
||||
"include/toml++"
|
||||
"include/Remotery/lib"
|
||||
"include/robin_hood"
|
||||
"include/libguarded"
|
||||
"include/tracy"
|
||||
"include/meshoptimizer"
|
||||
)
|
||||
|
||||
add_executable(univerxel "src/main.cpp" ${SOURCES} ${INCLUDE_SOURCES})
|
||||
target_compile_features(univerxel PUBLIC cxx_std_17)
|
||||
target_link_libraries(univerxel ${LINKED_LIBS})
|
||||
target_include_directories(univerxel PRIVATE ${INCLUDE_LIBS})
|
||||
target_compile_definitions(univerxel PRIVATE RMT_ENABLED=${PROFILING} RMT_USE_OPENGL=${PROFILING} FIXED_WINDOW=${FIXED_WINDOW} HN_USE_FILESYSTEM=1)
|
||||
if(PROFILING)
|
||||
target_compile_definitions(univerxel PRIVATE TRACY_ENABLE=1 FIXED_WINDOW=${FIXED_WINDOW} HN_USE_FILESYSTEM=1)
|
||||
else(PROFILING)
|
||||
target_compile_definitions(univerxel PRIVATE FIXED_WINDOW=${FIXED_WINDOW} HN_USE_FILESYSTEM=1)
|
||||
endif(PROFILING)
|
||||
|
||||
|
||||
file(COPY content/shaders DESTINATION ${CMAKE_BINARY_DIR}/content)
|
||||
file(COPY content/textures DESTINATION ${CMAKE_BINARY_DIR}/content)
|
||||
|
@ -70,9 +76,20 @@ file(GLOB SMP_SOURCES "src/zstd_sampler.cpp" "src/world/Chunk.cpp" "include/Fast
|
|||
add_executable(zstd_sampler EXCLUDE_FROM_ALL ${SMP_SOURCES})
|
||||
target_compile_features(zstd_sampler PUBLIC cxx_std_17)
|
||||
target_link_libraries(zstd_sampler ${LINKED_LIBS})
|
||||
target_include_directories(zstd_sampler PRIVATE "include/FastNoiseSIMD")
|
||||
target_include_directories(zstd_sampler PRIVATE "include/FastNoiseSIMD" "include/robin_hood")
|
||||
# target_compile_definitions(zstd_sampler PRIVATE HN_USE_FILESYSTEM=1)
|
||||
|
||||
add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/content/zstd.dict"
|
||||
COMMAND "${CMAKE_BINARY_DIR}/zstd_sampler" DEPENDS zstd_sampler)
|
||||
add_custom_target(generate_dictionary DEPENDS "${CMAKE_BINARY_DIR}/content/zstd.dict")
|
||||
add_custom_target(generate_dictionary DEPENDS "${CMAKE_BINARY_DIR}/content/zstd.dict")
|
||||
|
||||
# Builtin models
|
||||
file(GLOB_RECURSE MCT_SOURCES "src/model_contouring.cpp" "src/*/*.cpp")
|
||||
add_executable(model_contouring EXCLUDE_FROM_ALL ${MCT_SOURCES} ${INCLUDE_SOURCES})
|
||||
target_compile_features(model_contouring PUBLIC cxx_std_17)
|
||||
target_link_libraries(model_contouring ${LINKED_LIBS})
|
||||
target_include_directories(model_contouring PRIVATE ${INCLUDE_LIBS})
|
||||
|
||||
add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/content/model.ivb"
|
||||
COMMAND "${CMAKE_BINARY_DIR}/model_contouring" DEPENDS model_contouring)
|
||||
add_custom_target(render_models DEPENDS "${CMAKE_BINARY_DIR}/content/model.ivb")
|
|
@ -29,7 +29,7 @@ Experimental project using OpenGL.
|
|||
* ImGui
|
||||
* FasNoiseSIMD
|
||||
* Toml++
|
||||
* Remotery
|
||||
* Tracy
|
||||
* Love and insomnia
|
||||
|
||||
|
||||
|
@ -56,7 +56,7 @@ To get a local copy up and running, follow these simple steps.
|
|||
|
||||
1. Clone the project repo
|
||||
```sh
|
||||
git clone https://git.wadza.fr/me/univerxel.git
|
||||
git clone --recursive https://git.wadza.fr/me/univerxel.git
|
||||
```
|
||||
2. Create build folder and move
|
||||
```sh
|
||||
|
|
51
TODO.md
51
TODO.md
|
@ -4,8 +4,9 @@
|
|||
- [x] Generate noise
|
||||
- [x] Density
|
||||
- [x] Robin hood map
|
||||
- [ ] In memory RLE
|
||||
- [ ] Octree world
|
||||
- [ ] Memory usage
|
||||
- [ ] In memory RLE
|
||||
- [ ] Octree world
|
||||
- [x] Serialize
|
||||
- [x] Group files
|
||||
- [x] Zstd + custom grouping
|
||||
|
@ -17,22 +18,30 @@
|
|||
- [x] Edition
|
||||
- [~] Entity
|
||||
- [x] Basic
|
||||
- [ ] Instanced
|
||||
- [x] Instanced
|
||||
- https://learnopengl.com/Advanced-OpenGL/Instancing
|
||||
- [ ] Inheritance
|
||||
- [ ] ECS
|
||||
- [ ] Relative to area
|
||||
- [ ] Player as entity
|
||||
- [ ] Entities block world changes
|
||||
- [x] Area
|
||||
- [x] Offset
|
||||
- [ ] Rotation
|
||||
- [ ] Planet
|
||||
- [ ] CubicSphere
|
||||
- [~] Planet
|
||||
- [~] CubeSphere
|
||||
- [ ] Area corrected CubeSphere
|
||||
- [ ] Corrected Normals
|
||||
- [ ] Surface curvature
|
||||
- [ ] Curvature avare frustum
|
||||
- [x] Sphere
|
||||
- [ ] Healpix
|
||||
- [ ] Surface features
|
||||
- [ ] Biomes
|
||||
- https://imgur.com/kM8b5Zq
|
||||
- https://imgur.com/a/bh2iy
|
||||
- https://speciesdevblog.files.wordpress.com/2012/11/biomemap.png
|
||||
- [ ] Galaxy
|
||||
- [ ] Biomes
|
||||
- https://imgur.com/kM8b5Zq
|
||||
- https://imgur.com/a/bh2iy
|
||||
- https://speciesdevblog.files.wordpress.com/2012/11/biomemap.png
|
||||
- [ ] Leak test
|
||||
- Valgrind
|
||||
- Xtree-memory
|
||||
|
@ -41,6 +50,10 @@
|
|||
- [ ] clang -fall
|
||||
- [ ] Server
|
||||
- [ ] ZeroMQ
|
||||
- [~] Workers
|
||||
- [x] Basic
|
||||
- [x] Use system infos
|
||||
- [ ] Pool
|
||||
- [x] Logger
|
||||
- [ ] FastNoiseSIMD / HastyNoise double precision
|
||||
- [x] Generational identifier
|
||||
|
@ -65,20 +78,28 @@
|
|||
- [ ] Environment mapping
|
||||
- [ ] Deferred
|
||||
- [ ] Cascaded shadow maps
|
||||
- [ ] RayCast
|
||||
- [ ] Vulkan
|
||||
- [ ] Ray Tracing
|
||||
- [~] Transparency
|
||||
- [x] Float precision problem
|
||||
|
||||
## Contouring
|
||||
- [x] Box contouring
|
||||
- [x] Ignore sides
|
||||
- [ ] LOD
|
||||
- [~] LOD
|
||||
- [x] Generate lod
|
||||
- [x] Display lod
|
||||
- [x] Select level count
|
||||
- [ ] Group low lod buffers
|
||||
- [ ] Octree
|
||||
- [x] Dual MC
|
||||
- [ ] Octree
|
||||
- [ ] Collision
|
||||
- [~] Collision
|
||||
- [ ] Dynamic index size
|
||||
- [x] Chunk size performance
|
||||
- [ ] Render with glBufferSubData
|
||||
- [x] Frustum Culling
|
||||
- [ ] Occlusion Culling
|
||||
|
||||
- [~] Occlusion Culling
|
||||
- [x] Primitive raycast
|
||||
- [ ] Iterator ray
|
||||
- [ ] Cast from chunk center
|
||||
- [x] Document
|
|
@ -1,50 +0,0 @@
|
|||
#version 330 core
|
||||
|
||||
layout(location = 0) in vec3 Position_modelspace;
|
||||
layout(location = 1) in uint Material_model;
|
||||
layout(location = 2) in vec3 Normal_modelspace;
|
||||
|
||||
out VertexData {
|
||||
vec3 Position_modelspace;
|
||||
flat uint Material;
|
||||
vec3 FaceNormal_modelspace;
|
||||
#ifdef PBR
|
||||
vec3 FaceNormal_worldspace;
|
||||
vec3 EyeDirection_cameraspace;
|
||||
vec3 LightDirection_cameraspace;
|
||||
#endif
|
||||
#ifdef FOG
|
||||
float Depth;
|
||||
#endif
|
||||
} vs;
|
||||
|
||||
uniform mat4 MVP;
|
||||
uniform mat4 Model;
|
||||
uniform mat4 View;
|
||||
|
||||
uniform vec3 LightInvDirection_worldspace;
|
||||
uniform float FogDepth;
|
||||
|
||||
void main(){
|
||||
gl_Position = MVP * vec4(Position_modelspace, 1);
|
||||
vs.Position_modelspace = Position_modelspace;
|
||||
vec3 Position_worldspace = (Model * vec4(Position_modelspace,1)).xyz;
|
||||
|
||||
#ifdef FOG
|
||||
vs.Depth = length((View * vec4(Position_worldspace,1)).xyz) / FogDepth;
|
||||
#endif
|
||||
|
||||
vs.Material = Material_model;
|
||||
|
||||
vs.FaceNormal_modelspace = normalize(Normal_modelspace);
|
||||
#ifdef PBR
|
||||
vs.FaceNormal_worldspace = normalize((Model * vec4(vs.FaceNormal_modelspace, 0)).xyz);
|
||||
|
||||
// Vector that goes from the vertex to the camera, in camera space.
|
||||
// In camera space, the camera is at the origin (0,0,0).
|
||||
vs.EyeDirection_cameraspace = vec3(0,0,0) - (View * Model * vec4(Position_modelspace,1)).xyz;
|
||||
|
||||
// Vector that goes from the vertex to the light, in camera space
|
||||
vs.LightDirection_cameraspace = (View * vec4(LightInvDirection_worldspace,0)).xyz;
|
||||
#endif
|
||||
}
|
|
@ -18,10 +18,10 @@ in VertexData
|
|||
{
|
||||
vec3 Position_modelspace;
|
||||
#ifdef GEOMETRY
|
||||
flat uint Materials[3];
|
||||
vec3 MaterialRatio;
|
||||
flat uint Textures[3];
|
||||
vec3 TextureRatio;
|
||||
#else
|
||||
flat uint Material;
|
||||
flat uint Texture;
|
||||
#endif
|
||||
vec3 FaceNormal_modelspace;
|
||||
#ifdef PBR
|
||||
|
@ -38,27 +38,70 @@ vec3 expand(vec3 v) {
|
|||
return (v - 0.5) * 2;
|
||||
}
|
||||
|
||||
vec2 hash2D(vec2 s) {
|
||||
// magic numbers
|
||||
return fract(sin(mod(vec2(dot(s, vec2(127.1, 311.7)), dot(s, vec2(269.5, 183.3))), 3.14159)) * 43758.5453);
|
||||
}
|
||||
|
||||
vec4 textureStochastic(sampler2DArray sample, vec3 UV) {
|
||||
#ifdef STOCHASTIC
|
||||
// triangular by approx 2*sqrt(3)
|
||||
vec2 skewUV = mat2(1.0, 0.0, -0.57735027, 1.15470054) * (UV.xy * 3.46400);
|
||||
|
||||
// vertex id and barrycentric coords
|
||||
vec2 vxID = vec2(floor(skewUV));
|
||||
vec3 barry = vec3(fract(skewUV), 0);
|
||||
barry.z = 1.0 - barry.x - barry.y;
|
||||
|
||||
vec3 BW_vx0;
|
||||
vec3 BW_vx1;
|
||||
vec3 BW_vx2;
|
||||
vec3 BW_vx3;
|
||||
|
||||
if(barry.z > 0) {
|
||||
BW_vx0 = vec3(vxID, 0);
|
||||
BW_vx1 = vec3(vxID + vec2(0, 1), 0);
|
||||
BW_vx2 = vec3(vxID + vec2(1, 0), 0);
|
||||
BW_vx3 = barry.zyx;
|
||||
} else {
|
||||
BW_vx0 = vec3(vxID + vec2(1, 1), 0);
|
||||
BW_vx1 = vec3(vxID + vec2(1, 0), 0);
|
||||
BW_vx2 = vec3(vxID + vec2(0, 1), 0);
|
||||
BW_vx3 = vec3(-barry.z, 1.0 - barry.y, 1.0 - barry.x);
|
||||
}
|
||||
|
||||
vec2 dx = dFdx(UV.xy);
|
||||
vec2 dy = dFdy(UV.xy);
|
||||
|
||||
return textureGrad(sample, vec3(UV.xy + hash2D(BW_vx0.xy), UV.z), dx, dy) * BW_vx3.x +
|
||||
textureGrad(sample, vec3(UV.xy + hash2D(BW_vx1.xy), UV.z), dx, dy) * BW_vx3.y +
|
||||
textureGrad(sample, vec3(UV.xy + hash2D(BW_vx2.xy), UV.z), dx, dy) * BW_vx3.z;
|
||||
#else
|
||||
return texture(sample, UV);
|
||||
#endif
|
||||
}
|
||||
|
||||
vec4 getTexture(sampler2DArray sample, vec2 UV) {
|
||||
#ifdef GEOMETRY
|
||||
#ifdef BLEND
|
||||
vec4 colx = texture(sample, vec3(UV, vs.Materials[0]));
|
||||
if(vs.Materials[1] == vs.Materials[0]) {
|
||||
return vs.Materials[2] == vs.Materials[0] ? colx :
|
||||
mix(colx, texture(sample, vec3(UV, vs.Materials[2])), vs.MaterialRatio.z);
|
||||
vec4 colx = textureStochastic(sample, vec3(UV, vs.Textures[0]));
|
||||
if(vs.Textures[1] == vs.Textures[0]) {
|
||||
return vs.Textures[2] == vs.Textures[0] ? colx :
|
||||
mix(colx, textureStochastic(sample, vec3(UV, vs.Textures[2])), vs.TextureRatio.z);
|
||||
} else {
|
||||
vec4 coly = texture(sample, vec3(UV, vs.Materials[1]));
|
||||
return vs.Materials[2] == vs.Materials[0] ? mix(colx, coly, vs.MaterialRatio.y) : (
|
||||
vs.Materials[2] == vs.Materials[1] ? mix(coly, colx, vs.MaterialRatio.x) :
|
||||
colx * vs.MaterialRatio.x + coly * vs.MaterialRatio.y + texture(sample, vec3(UV, vs.Materials[2])) * vs.MaterialRatio.z);
|
||||
vec4 coly = textureStochastic(sample, vec3(UV, vs.Textures[1]));
|
||||
return vs.Textures[2] == vs.Textures[0] ? mix(colx, coly, vs.TextureRatio.y) : (
|
||||
vs.Textures[2] == vs.Textures[1] ? mix(coly, colx, vs.TextureRatio.x) :
|
||||
colx * vs.TextureRatio.x + coly * vs.TextureRatio.y + textureStochastic(sample, vec3(UV, vs.Textures[2])) * vs.TextureRatio.z);
|
||||
}
|
||||
#else
|
||||
int mainMaterial = vs.MaterialRatio.x >= vs.MaterialRatio.y ?
|
||||
(vs.MaterialRatio.x >= vs.MaterialRatio.z ? 0 : 2) :
|
||||
(vs.MaterialRatio.y >= vs.MaterialRatio.z ? 1 : 2);
|
||||
return texture(sample, vec3(UV, vs.Materials[mainMaterial]));
|
||||
int mainTexture = vs.TextureRatio.x >= vs.TextureRatio.y ?
|
||||
(vs.TextureRatio.x >= vs.TextureRatio.z ? 0 : 2) :
|
||||
(vs.TextureRatio.y >= vs.TextureRatio.z ? 1 : 2);
|
||||
return textureStochastic(sample, vec3(UV, vs.Textures[mainTexture]));
|
||||
#endif
|
||||
#else
|
||||
return texture(sample, vec3(UV, vs.Material));
|
||||
return textureStochastic(sample, vec3(UV, vs.Texture));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -119,10 +162,10 @@ void main() {
|
|||
|
||||
// Colors
|
||||
#ifdef PBR
|
||||
// Material properties
|
||||
vec3 MaterialDiffuseColor = tex;
|
||||
vec3 MaterialAmbientColor = vec3(.1) * MaterialDiffuseColor * texHOS.y;
|
||||
vec3 MaterialSpecularColor = vec3(.8) * texHOS.z;
|
||||
// Texture properties
|
||||
vec3 TextureDiffuseColor = tex;
|
||||
vec3 TextureAmbientColor = vec3(.1) * TextureDiffuseColor * texHOS.y;
|
||||
vec3 TextureSpecularColor = vec3(.8) * texHOS.z;
|
||||
|
||||
vec3 Normal_cameraspace = normalize((View * vec4(worldNormal,0)).xyz);
|
||||
|
||||
|
@ -159,11 +202,11 @@ void main() {
|
|||
|
||||
color =
|
||||
// Ambient : simulates indirect lighting
|
||||
MaterialAmbientColor +
|
||||
TextureAmbientColor +
|
||||
// Diffuse : "color" of the object
|
||||
visibility * MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance * distance) +
|
||||
visibility * TextureDiffuseColor * LightColor * LightPower * cosTheta / (distance * distance) +
|
||||
// Specular : reflective highlight, like a mirror
|
||||
visibility * MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance * distance);
|
||||
visibility * TextureSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance * distance);
|
||||
#else
|
||||
color = tex;
|
||||
#endif
|
||||
|
@ -172,4 +215,7 @@ void main() {
|
|||
color = mix(color, pow(FogColor, vec3(2.2)), clamp(ratio, 0, 1));
|
||||
#endif
|
||||
color = pow(color, vec3(1.0 / 2.2));
|
||||
if(color.r > 1 || color.g > 1 || color.b > 1) {
|
||||
color = vec3(1, 0, 0); //TODO: bloom
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ layout (triangle_strip, max_vertices = 3) out;
|
|||
|
||||
in VertexData {
|
||||
vec3 Position_modelspace;
|
||||
flat uint Material;
|
||||
flat uint Texture;
|
||||
vec3 FaceNormal_modelspace;
|
||||
#ifdef PBR
|
||||
vec3 FaceNormal_worldspace;
|
||||
|
@ -20,8 +20,8 @@ in VertexData {
|
|||
|
||||
out GeometryData {
|
||||
vec3 Position_modelspace;
|
||||
flat uint Materials[3];
|
||||
vec3 MaterialRatio;
|
||||
flat uint Textures[3];
|
||||
vec3 TextureRatio;
|
||||
vec3 FaceNormal_modelspace;
|
||||
#ifdef PBR
|
||||
vec3 FaceNormal_worldspace;
|
||||
|
@ -39,26 +39,28 @@ void main() {
|
|||
gl_Position = gl_in[i].gl_Position;
|
||||
gs.Position_modelspace = vs_in[i].Position_modelspace;
|
||||
gs.FaceNormal_modelspace = vs_in[i].FaceNormal_modelspace;
|
||||
gs.FaceNormal_worldspace = vs_in[i].FaceNormal_worldspace;
|
||||
|
||||
gs.Materials[i] = vs_in[i].Material;
|
||||
gs.Textures[i] = vs_in[i].Texture;
|
||||
switch(int(mod(i,3))) {
|
||||
case 0:
|
||||
gs.MaterialRatio = vec3(1,0,0);
|
||||
gs.TextureRatio = vec3(1,0,0);
|
||||
break;
|
||||
case 1:
|
||||
gs.MaterialRatio = vec3(0,1,0);
|
||||
gs.TextureRatio = vec3(0,1,0);
|
||||
break;
|
||||
case 2:
|
||||
gs.MaterialRatio = vec3(0,0,1);
|
||||
gs.TextureRatio = vec3(0,0,1);
|
||||
break;
|
||||
default:
|
||||
gs.MaterialRatio = vec3(0);
|
||||
gs.TextureRatio = vec3(0);
|
||||
break;
|
||||
};
|
||||
|
||||
#ifdef PBR
|
||||
gs.FaceNormal_worldspace = vs_in[i].FaceNormal_worldspace;
|
||||
gs.EyeDirection_cameraspace = vs_in[i].EyeDirection_cameraspace;
|
||||
gs.LightDirection_cameraspace = vs_in[i].LightDirection_cameraspace;
|
||||
#endif
|
||||
#ifdef SHADOW
|
||||
gs.ShadowCoord = vs_in[i].ShadowCoord;
|
||||
#endif
|
|
@ -0,0 +1,72 @@
|
|||
#version 330 core
|
||||
|
||||
layout(location = 0) in vec3 Position_modelspace;
|
||||
layout(location = 1) in uint Texture_model;
|
||||
layout(location = 2) in vec3 Normal_modelspace;
|
||||
|
||||
out VertexData {
|
||||
vec3 Position_modelspace;
|
||||
flat uint Texture;
|
||||
vec3 FaceNormal_modelspace;
|
||||
#ifdef PBR
|
||||
vec3 FaceNormal_worldspace;
|
||||
vec3 EyeDirection_cameraspace;
|
||||
vec3 LightDirection_cameraspace;
|
||||
#endif
|
||||
#ifdef FOG
|
||||
float Depth;
|
||||
#endif
|
||||
} vs;
|
||||
|
||||
#ifdef INSTANCED
|
||||
layout(location = 6) in mat4 Model;
|
||||
#else
|
||||
uniform mat4 Model;
|
||||
#endif
|
||||
uniform mat4 View;
|
||||
uniform mat4 Proj;
|
||||
|
||||
uniform vec4 SphereProj;
|
||||
uniform float Curvature;
|
||||
|
||||
uniform vec3 LightInvDirection_worldspace;
|
||||
uniform float FogDepth;
|
||||
|
||||
void main(){
|
||||
vs.Position_modelspace = Position_modelspace;
|
||||
|
||||
#ifdef CURVATURE
|
||||
if(Curvature > 0) {
|
||||
vec3 Position_areaspace = Position_modelspace + SphereProj.xyz;
|
||||
vec2 sph = vec2(acos(Position_areaspace.z / length(Position_areaspace.xyz)), atan(Position_areaspace.y, Position_areaspace.x));
|
||||
#ifdef CURV_DEPTH
|
||||
float radius = max(max(abs(Position_areaspace.x), abs(Position_areaspace.y)), abs(Position_areaspace.z));
|
||||
#else
|
||||
float radius = SphereProj.w;
|
||||
#endif
|
||||
vs.Position_modelspace = mix(vs.Position_modelspace, vec3(sin(sph.x)*cos(sph.y), sin(sph.x)*sin(sph.y), cos(sph.x)) * radius - SphereProj.xyz, Curvature);
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 Position_cameraspace = View * Model * vec4(vs.Position_modelspace, 1);
|
||||
gl_Position = Proj * Position_cameraspace;
|
||||
|
||||
#ifdef FOG
|
||||
vs.Depth = length(Position_cameraspace.xyz) / FogDepth;
|
||||
#endif
|
||||
|
||||
vs.Texture = Texture_model;
|
||||
|
||||
vs.FaceNormal_modelspace = normalize(Normal_modelspace);
|
||||
#ifdef PBR
|
||||
// TODO: correct normal
|
||||
vs.FaceNormal_worldspace = normalize((Model * vec4(vs.FaceNormal_modelspace, 0)).xyz);
|
||||
|
||||
// Vector that goes from the vertex to the camera, in camera space.
|
||||
// In camera space, the camera is at the origin (0,0,0).
|
||||
vs.EyeDirection_cameraspace = vec3(0,0,0) - Position_cameraspace.xyz;
|
||||
|
||||
// Vector that goes from the vertex to the light, in camera space
|
||||
vs.LightDirection_cameraspace = (View * vec4(LightInvDirection_worldspace,0)).xyz;
|
||||
#endif
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
content/textures-src/1024-realistic/Ground_Forest_002_ambientOcclusion.jpg (Stored with Git LFS)
Normal file
BIN
content/textures-src/1024-realistic/Ground_Forest_002_ambientOcclusion.jpg (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/textures-src/1024-realistic/Ground_Forest_002_baseColor.jpg (Stored with Git LFS)
Normal file
BIN
content/textures-src/1024-realistic/Ground_Forest_002_baseColor.jpg (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/textures-src/1024-realistic/Ground_Forest_002_height.png (Stored with Git LFS)
Normal file
BIN
content/textures-src/1024-realistic/Ground_Forest_002_height.png (Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
BIN
content/textures-src/1024-realistic/Ground_Forest_002_normal.jpg (Stored with Git LFS)
Normal file
BIN
content/textures-src/1024-realistic/Ground_Forest_002_normal.jpg (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/textures-src/1024-realistic/Ground_Forest_002_roughness.jpg (Stored with Git LFS)
Normal file
BIN
content/textures-src/1024-realistic/Ground_Forest_002_roughness.jpg (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/textures-src/1024-realistic/Ground_Forest_003_ROUGH.jpg (Stored with Git LFS)
Normal file
BIN
content/textures-src/1024-realistic/Ground_Forest_003_ROUGH.jpg (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/textures-src/1024-realistic/Ground_Forest_003_ambientOcclusion.jpg (Stored with Git LFS)
Normal file
BIN
content/textures-src/1024-realistic/Ground_Forest_003_ambientOcclusion.jpg (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/textures-src/1024-realistic/Ground_Forest_003_baseColor.jpg (Stored with Git LFS)
Normal file
BIN
content/textures-src/1024-realistic/Ground_Forest_003_baseColor.jpg (Stored with Git LFS)
Normal file
Binary file not shown.
BIN
content/textures-src/1024-realistic/Ground_Forest_003_height.png (Stored with Git LFS)
Normal file
BIN
content/textures-src/1024-realistic/Ground_Forest_003_height.png (Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
BIN
content/textures-src/1024-realistic/Ground_Forest_003_normal.jpg (Stored with Git LFS)
Normal file
BIN
content/textures-src/1024-realistic/Ground_Forest_003_normal.jpg (Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
content/textures/1024-realistic/terrain/Stone_path.hos.dds (Stored with Git LFS)
BIN
content/textures/1024-realistic/terrain/Stone_path.hos.dds (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,175 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
File diff suppressed because it is too large
Load Diff
|
@ -1,659 +0,0 @@
|
|||
|
||||
|
||||
/*
|
||||
Copyright 2014-2018 Celtoys Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Compiling
|
||||
---------
|
||||
|
||||
* Windows (MSVC) - add lib/Remotery.c and lib/Remotery.h to your program. Set include
|
||||
directories to add Remotery/lib path. The required library ws2_32.lib should be picked
|
||||
up through the use of the #pragma comment(lib, "ws2_32.lib") directive in Remotery.c.
|
||||
|
||||
* Mac OS X (XCode) - simply add lib/Remotery.c and lib/Remotery.h to your program.
|
||||
|
||||
* Linux (GCC) - add the source in lib folder. Compilation of the code requires -pthreads for
|
||||
library linkage. For example to compile the same run: cc lib/Remotery.c sample/sample.c
|
||||
-I lib -pthread -lm
|
||||
|
||||
You can define some extra macros to modify what features are compiled into Remotery. These are
|
||||
documented just below this comment.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef RMT_INCLUDED_H
|
||||
#define RMT_INCLUDED_H
|
||||
|
||||
|
||||
// Set to 0 to not include any bits of Remotery in your build
|
||||
#ifndef RMT_ENABLED
|
||||
#define RMT_ENABLED 1
|
||||
#endif
|
||||
|
||||
// Help performance of the server sending data to the client by marking this machine as little-endian
|
||||
#ifndef RMT_ASSUME_LITTLE_ENDIAN
|
||||
#define RMT_ASSUME_LITTLE_ENDIAN 0
|
||||
#endif
|
||||
|
||||
// Used by the Celtoys TinyCRT library (not released yet)
|
||||
#ifndef RMT_USE_TINYCRT
|
||||
#define RMT_USE_TINYCRT 0
|
||||
#endif
|
||||
|
||||
// Assuming CUDA headers/libs are setup, allow CUDA profiling
|
||||
#ifndef RMT_USE_CUDA
|
||||
#define RMT_USE_CUDA 0
|
||||
#endif
|
||||
|
||||
// Assuming Direct3D 11 headers/libs are setup, allow D3D11 profiling
|
||||
#ifndef RMT_USE_D3D11
|
||||
#define RMT_USE_D3D11 0
|
||||
#endif
|
||||
|
||||
// Allow OpenGL profiling
|
||||
#ifndef RMT_USE_OPENGL
|
||||
#define RMT_USE_OPENGL 0
|
||||
#endif
|
||||
|
||||
// Allow Metal profiling
|
||||
#ifndef RMT_USE_METAL
|
||||
#define RMT_USE_METAL 0
|
||||
#endif
|
||||
|
||||
// Initially use POSIX thread names to name threads instead of Thread0, 1, ...
|
||||
#ifndef RMT_USE_POSIX_THREADNAMES
|
||||
#define RMT_USE_POSIX_THREADNAMES 0
|
||||
#endif
|
||||
|
||||
// How many times we spin data back and forth between CPU & GPU
|
||||
// to calculate average RTT (Roundtrip Time). Cannot be 0.
|
||||
// Affects OpenGL & D3D11
|
||||
#ifndef RMT_GPU_CPU_SYNC_NUM_ITERATIONS
|
||||
#define RMT_GPU_CPU_SYNC_NUM_ITERATIONS 16
|
||||
#endif
|
||||
|
||||
// Time in seconds between each resync to compensate for drifting between GPU & CPU timers,
|
||||
// effects of power saving, etc. Resyncs can cause stutter, lag spikes, stalls.
|
||||
// Set to 0 for never.
|
||||
// Affects OpenGL & D3D11
|
||||
#ifndef RMT_GPU_CPU_SYNC_SECONDS
|
||||
#define RMT_GPU_CPU_SYNC_SECONDS 30
|
||||
#endif
|
||||
|
||||
// Whether we should automatically resync if we detect a timer disjoint (e.g.
|
||||
// changed from AC power to battery, GPU is overheating, or throttling up/down
|
||||
// due to laptop savings events). Set it to 0 to avoid resync in such events.
|
||||
// Useful if for some odd reason a driver reports a lot of disjoints.
|
||||
// Affects D3D11
|
||||
#ifndef RMT_D3D11_RESYNC_ON_DISJOINT
|
||||
#define RMT_D3D11_RESYNC_ON_DISJOINT 1
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
Compiler/Platform Detection and Preprocessor Utilities
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
// Platform identification
|
||||
#if defined(_WINDOWS) || defined(_WIN32)
|
||||
#define RMT_PLATFORM_WINDOWS
|
||||
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
#define RMT_PLATFORM_LINUX
|
||||
#define RMT_PLATFORM_POSIX
|
||||
#elif defined(__APPLE__)
|
||||
#define RMT_PLATFORM_MACOS
|
||||
#define RMT_PLATFORM_POSIX
|
||||
#endif
|
||||
|
||||
#ifdef RMT_DLL
|
||||
#if defined (RMT_PLATFORM_WINDOWS)
|
||||
#if defined (RMT_IMPL)
|
||||
#define RMT_API __declspec(dllexport)
|
||||
#else
|
||||
#define RMT_API __declspec(dllimport)
|
||||
#endif
|
||||
#elif defined (RMT_PLATFORM_POSIX)
|
||||
#if defined (RMT_IMPL)
|
||||
#define RMT_API __attribute__((visibility("default")))
|
||||
#else
|
||||
#define RMT_API
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#define RMT_API
|
||||
#endif
|
||||
|
||||
// Allows macros to be written that can work around the inability to do: #define(x) #ifdef x
|
||||
// with the C preprocessor.
|
||||
#if RMT_ENABLED
|
||||
#define IFDEF_RMT_ENABLED(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_ENABLED(t, f) f
|
||||
#endif
|
||||
#if RMT_ENABLED && RMT_USE_CUDA
|
||||
#define IFDEF_RMT_USE_CUDA(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_USE_CUDA(t, f) f
|
||||
#endif
|
||||
#if RMT_ENABLED && RMT_USE_D3D11
|
||||
#define IFDEF_RMT_USE_D3D11(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_USE_D3D11(t, f) f
|
||||
#endif
|
||||
#if RMT_ENABLED && RMT_USE_OPENGL
|
||||
#define IFDEF_RMT_USE_OPENGL(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_USE_OPENGL(t, f) f
|
||||
#endif
|
||||
#if RMT_ENABLED && RMT_USE_METAL
|
||||
#define IFDEF_RMT_USE_METAL(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_USE_METAL(t, f) f
|
||||
#endif
|
||||
|
||||
|
||||
// Public interface is written in terms of these macros to easily enable/disable itself
|
||||
#define RMT_OPTIONAL(macro, x) IFDEF_ ## macro(x, )
|
||||
#define RMT_OPTIONAL_RET(macro, x, y) IFDEF_ ## macro(x, (y))
|
||||
|
||||
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
Types
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// Boolean
|
||||
typedef unsigned int rmtBool;
|
||||
#define RMT_TRUE ((rmtBool)1)
|
||||
#define RMT_FALSE ((rmtBool)0)
|
||||
|
||||
|
||||
// Unsigned integer types
|
||||
typedef unsigned char rmtU8;
|
||||
typedef unsigned short rmtU16;
|
||||
typedef unsigned int rmtU32;
|
||||
typedef unsigned long long rmtU64;
|
||||
|
||||
|
||||
// Signed integer types
|
||||
typedef char rmtS8;
|
||||
typedef short rmtS16;
|
||||
typedef int rmtS32;
|
||||
typedef long long rmtS64;
|
||||
|
||||
|
||||
// Const, null-terminated string pointer
|
||||
typedef const char* rmtPStr;
|
||||
|
||||
|
||||
// Handle to the main remotery instance
|
||||
typedef struct Remotery Remotery;
|
||||
|
||||
|
||||
// All possible error codes
|
||||
typedef enum rmtError
|
||||
{
|
||||
RMT_ERROR_NONE,
|
||||
RMT_ERROR_RECURSIVE_SAMPLE, // Not an error but an internal message to calling code
|
||||
|
||||
// System errors
|
||||
RMT_ERROR_MALLOC_FAIL, // Malloc call within remotery failed
|
||||
RMT_ERROR_TLS_ALLOC_FAIL, // Attempt to allocate thread local storage failed
|
||||
RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL, // Failed to create a virtual memory mirror buffer
|
||||
RMT_ERROR_CREATE_THREAD_FAIL, // Failed to create a thread for the server
|
||||
|
||||
// Network TCP/IP socket errors
|
||||
RMT_ERROR_SOCKET_INIT_NETWORK_FAIL, // Network initialisation failure (e.g. on Win32, WSAStartup fails)
|
||||
RMT_ERROR_SOCKET_CREATE_FAIL, // Can't create a socket for connection to the remote viewer
|
||||
RMT_ERROR_SOCKET_BIND_FAIL, // Can't bind a socket for the server
|
||||
RMT_ERROR_SOCKET_LISTEN_FAIL, // Created server socket failed to enter a listen state
|
||||
RMT_ERROR_SOCKET_SET_NON_BLOCKING_FAIL, // Created server socket failed to switch to a non-blocking state
|
||||
RMT_ERROR_SOCKET_INVALID_POLL, // Poll attempt on an invalid socket
|
||||
RMT_ERROR_SOCKET_SELECT_FAIL, // Server failed to call select on socket
|
||||
RMT_ERROR_SOCKET_POLL_ERRORS, // Poll notified that the socket has errors
|
||||
RMT_ERROR_SOCKET_ACCEPT_FAIL, // Server failed to accept connection from client
|
||||
RMT_ERROR_SOCKET_SEND_TIMEOUT, // Timed out trying to send data
|
||||
RMT_ERROR_SOCKET_SEND_FAIL, // Unrecoverable error occured while client/server tried to send data
|
||||
RMT_ERROR_SOCKET_RECV_NO_DATA, // No data available when attempting a receive
|
||||
RMT_ERROR_SOCKET_RECV_TIMEOUT, // Timed out trying to receive data
|
||||
RMT_ERROR_SOCKET_RECV_FAILED, // Unrecoverable error occured while client/server tried to receive data
|
||||
|
||||
// WebSocket errors
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_NOT_GET, // WebSocket server handshake failed, not HTTP GET
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_VERSION, // WebSocket server handshake failed, can't locate WebSocket version
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_VERSION, // WebSocket server handshake failed, unsupported WebSocket version
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_HOST, // WebSocket server handshake failed, can't locate host
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_HOST, // WebSocket server handshake failed, host is not allowed to connect
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_KEY, // WebSocket server handshake failed, can't locate WebSocket key
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_KEY, // WebSocket server handshake failed, WebSocket key is ill-formed
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL, // WebSocket server handshake failed, internal error, bad string code
|
||||
RMT_ERROR_WEBSOCKET_DISCONNECTED, // WebSocket server received a disconnect request and closed the socket
|
||||
RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER, // Couldn't parse WebSocket frame header
|
||||
RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER_SIZE, // Partially received wide frame header size
|
||||
RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER_MASK, // Partially received frame header data mask
|
||||
RMT_ERROR_WEBSOCKET_RECEIVE_TIMEOUT, // Timeout receiving frame header
|
||||
|
||||
RMT_ERROR_REMOTERY_NOT_CREATED, // Remotery object has not been created
|
||||
RMT_ERROR_SEND_ON_INCOMPLETE_PROFILE, // An attempt was made to send an incomplete profile tree to the client
|
||||
|
||||
// CUDA error messages
|
||||
RMT_ERROR_CUDA_DEINITIALIZED, // This indicates that the CUDA driver is in the process of shutting down
|
||||
RMT_ERROR_CUDA_NOT_INITIALIZED, // This indicates that the CUDA driver has not been initialized with cuInit() or that initialization has failed
|
||||
RMT_ERROR_CUDA_INVALID_CONTEXT, // This most frequently indicates that there is no context bound to the current thread
|
||||
RMT_ERROR_CUDA_INVALID_VALUE, // This indicates that one or more of the parameters passed to the API call is not within an acceptable range of values
|
||||
RMT_ERROR_CUDA_INVALID_HANDLE, // This indicates that a resource handle passed to the API call was not valid
|
||||
RMT_ERROR_CUDA_OUT_OF_MEMORY, // The API call failed because it was unable to allocate enough memory to perform the requested operation
|
||||
RMT_ERROR_ERROR_NOT_READY, // This indicates that a resource handle passed to the API call was not valid
|
||||
|
||||
// Direct3D 11 error messages
|
||||
RMT_ERROR_D3D11_FAILED_TO_CREATE_QUERY, // Failed to create query for sample
|
||||
|
||||
// OpenGL error messages
|
||||
RMT_ERROR_OPENGL_ERROR, // Generic OpenGL error, no need to expose detail since app will need an OpenGL error callback registered
|
||||
|
||||
RMT_ERROR_CUDA_UNKNOWN,
|
||||
} rmtError;
|
||||
|
||||
|
||||
typedef enum rmtSampleFlags
|
||||
{
|
||||
// Default behaviour
|
||||
RMTSF_None = 0,
|
||||
|
||||
// Search parent for same-named samples and merge timing instead of adding a new sample
|
||||
RMTSF_Aggregate = 1,
|
||||
|
||||
// Merge sample with parent if it's the same sample
|
||||
RMTSF_Recursive = 2,
|
||||
} rmtSampleFlags;
|
||||
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
Public Interface
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// Can call remotery functions on a null pointer
|
||||
// TODO: Can embed extern "C" in these macros?
|
||||
|
||||
#define rmt_Settings() \
|
||||
RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_Settings(), NULL )
|
||||
|
||||
#define rmt_CreateGlobalInstance(rmt) \
|
||||
RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_CreateGlobalInstance(rmt), RMT_ERROR_NONE)
|
||||
|
||||
#define rmt_DestroyGlobalInstance(rmt) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_DestroyGlobalInstance(rmt))
|
||||
|
||||
#define rmt_SetGlobalInstance(rmt) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_SetGlobalInstance(rmt))
|
||||
|
||||
#define rmt_GetGlobalInstance() \
|
||||
RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_GetGlobalInstance(), NULL)
|
||||
|
||||
#define rmt_SetCurrentThreadName(rmt) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_SetCurrentThreadName(rmt))
|
||||
|
||||
#define rmt_LogText(text) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_LogText(text))
|
||||
|
||||
#define rmt_BeginCPUSample(name, flags) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, { \
|
||||
static rmtU32 rmt_sample_hash_##name = 0; \
|
||||
_rmt_BeginCPUSample(#name, flags, &rmt_sample_hash_##name); \
|
||||
})
|
||||
|
||||
#define rmt_BeginCPUSampleDynamic(namestr, flags) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_BeginCPUSample(namestr, flags, NULL))
|
||||
|
||||
#define rmt_EndCPUSample() \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_EndCPUSample())
|
||||
|
||||
|
||||
// Callback function pointer types
|
||||
typedef void* (*rmtMallocPtr)(void* mm_context, rmtU32 size);
|
||||
typedef void* (*rmtReallocPtr)(void* mm_context, void* ptr, rmtU32 size);
|
||||
typedef void (*rmtFreePtr)(void* mm_context, void* ptr);
|
||||
typedef void (*rmtInputHandlerPtr)(const char* text, void* context);
|
||||
|
||||
|
||||
// Struture to fill in to modify Remotery default settings
|
||||
typedef struct rmtSettings
|
||||
{
|
||||
// Which port to listen for incoming connections on
|
||||
rmtU16 port;
|
||||
|
||||
// When this server exits it can leave the port open in TIME_WAIT state for
|
||||
// a while. This forces subsequent server bind attempts to fail when
|
||||
// restarting. If you find restarts fail repeatedly with bind attempts, set
|
||||
// this to true to forcibly reuse the open port.
|
||||
rmtBool reuse_open_port;
|
||||
|
||||
// Only allow connections on localhost?
|
||||
// For dev builds you may want to access your game from other devices but if
|
||||
// you distribute a game to your players with Remotery active, probably best
|
||||
// to limit connections to localhost.
|
||||
rmtBool limit_connections_to_localhost;
|
||||
|
||||
// How long to sleep between server updates, hopefully trying to give
|
||||
// a little CPU back to other threads.
|
||||
rmtU32 msSleepBetweenServerUpdates;
|
||||
|
||||
// Size of the internal message queues Remotery uses
|
||||
// Will be rounded to page granularity of 64k
|
||||
rmtU32 messageQueueSizeInBytes;
|
||||
|
||||
// If the user continuously pushes to the message queue, the server network
|
||||
// code won't get a chance to update unless there's an upper-limit on how
|
||||
// many messages can be consumed per loop.
|
||||
rmtU32 maxNbMessagesPerUpdate;
|
||||
|
||||
// Callback pointers for memory allocation
|
||||
rmtMallocPtr malloc;
|
||||
rmtReallocPtr realloc;
|
||||
rmtFreePtr free;
|
||||
void* mm_context;
|
||||
|
||||
// Callback pointer for receiving input from the Remotery console
|
||||
rmtInputHandlerPtr input_handler;
|
||||
|
||||
// Context pointer that gets sent to Remotery console callback function
|
||||
void* input_handler_context;
|
||||
|
||||
rmtPStr logFilename;
|
||||
} rmtSettings;
|
||||
|
||||
|
||||
// Structure to fill in when binding CUDA to Remotery
|
||||
typedef struct rmtCUDABind
|
||||
{
|
||||
// The main context that all driver functions apply before each call
|
||||
void* context;
|
||||
|
||||
// Driver API function pointers that need to be pointed to
|
||||
// Untyped so that the CUDA headers are not required in this file
|
||||
// NOTE: These are named differently to the CUDA functions because the CUDA API has a habit of using
|
||||
// macros to point function calls to different versions, e.g. cuEventDestroy is a macro for
|
||||
// cuEventDestroy_v2.
|
||||
void* CtxSetCurrent;
|
||||
void* CtxGetCurrent;
|
||||
void* EventCreate;
|
||||
void* EventDestroy;
|
||||
void* EventRecord;
|
||||
void* EventQuery;
|
||||
void* EventElapsedTime;
|
||||
|
||||
} rmtCUDABind;
|
||||
|
||||
|
||||
// Call once after you've initialised CUDA to bind it to Remotery
|
||||
#define rmt_BindCUDA(bind) \
|
||||
RMT_OPTIONAL(RMT_USE_CUDA, _rmt_BindCUDA(bind))
|
||||
|
||||
// Mark the beginning of a CUDA sample on the specified asynchronous stream
|
||||
#define rmt_BeginCUDASample(name, stream) \
|
||||
RMT_OPTIONAL(RMT_USE_CUDA, { \
|
||||
static rmtU32 rmt_sample_hash_##name = 0; \
|
||||
_rmt_BeginCUDASample(#name, &rmt_sample_hash_##name, stream); \
|
||||
})
|
||||
|
||||
// Mark the end of a CUDA sample on the specified asynchronous stream
|
||||
#define rmt_EndCUDASample(stream) \
|
||||
RMT_OPTIONAL(RMT_USE_CUDA, _rmt_EndCUDASample(stream))
|
||||
|
||||
|
||||
#define rmt_BindD3D11(device, context) \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, _rmt_BindD3D11(device, context))
|
||||
|
||||
#define rmt_UnbindD3D11() \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, _rmt_UnbindD3D11())
|
||||
|
||||
#define rmt_BeginD3D11Sample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, { \
|
||||
static rmtU32 rmt_sample_hash_##name = 0; \
|
||||
_rmt_BeginD3D11Sample(#name, &rmt_sample_hash_##name); \
|
||||
})
|
||||
|
||||
#define rmt_BeginD3D11SampleDynamic(namestr) \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, _rmt_BeginD3D11Sample(namestr, NULL))
|
||||
|
||||
#define rmt_EndD3D11Sample() \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, _rmt_EndD3D11Sample())
|
||||
|
||||
|
||||
#define rmt_BindOpenGL() \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_BindOpenGL())
|
||||
|
||||
#define rmt_UnbindOpenGL() \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_UnbindOpenGL())
|
||||
|
||||
#define rmt_BeginOpenGLSample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, { \
|
||||
static rmtU32 rmt_sample_hash_##name = 0; \
|
||||
_rmt_BeginOpenGLSample(#name, &rmt_sample_hash_##name); \
|
||||
})
|
||||
|
||||
#define rmt_BeginOpenGLSampleDynamic(namestr) \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_BeginOpenGLSample(namestr, NULL))
|
||||
|
||||
#define rmt_EndOpenGLSample() \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_EndOpenGLSample())
|
||||
|
||||
|
||||
#define rmt_BindMetal(command_buffer) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_BindMetal(command_buffer));
|
||||
|
||||
#define rmt_UnbindMetal() \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_UnbindMetal());
|
||||
|
||||
#define rmt_BeginMetalSample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, { \
|
||||
static rmtU32 rmt_sample_hash_##name = 0; \
|
||||
_rmt_BeginMetalSample(#name, &rmt_sample_hash_##name); \
|
||||
})
|
||||
|
||||
#define rmt_BeginMetalSampleDynamic(namestr) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_BeginMetalSample(namestr, NULL))
|
||||
|
||||
#define rmt_EndMetalSample() \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_EndMetalSample())
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
C++ Public Interface Extensions
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
|
||||
#if RMT_ENABLED
|
||||
|
||||
// Types that end samples in their destructors
|
||||
extern "C" RMT_API void _rmt_EndCPUSample(void);
|
||||
struct rmt_EndCPUSampleOnScopeExit
|
||||
{
|
||||
~rmt_EndCPUSampleOnScopeExit()
|
||||
{
|
||||
_rmt_EndCPUSample();
|
||||
}
|
||||
};
|
||||
#if RMT_USE_CUDA
|
||||
extern "C" RMT_API void _rmt_EndCUDASample(void* stream);
|
||||
struct rmt_EndCUDASampleOnScopeExit
|
||||
{
|
||||
rmt_EndCUDASampleOnScopeExit(void* stream) : stream(stream)
|
||||
{
|
||||
}
|
||||
~rmt_EndCUDASampleOnScopeExit()
|
||||
{
|
||||
_rmt_EndCUDASample(stream);
|
||||
}
|
||||
void* stream;
|
||||
};
|
||||
#endif
|
||||
#if RMT_USE_D3D11
|
||||
extern "C" RMT_API void _rmt_EndD3D11Sample(void);
|
||||
struct rmt_EndD3D11SampleOnScopeExit
|
||||
{
|
||||
~rmt_EndD3D11SampleOnScopeExit()
|
||||
{
|
||||
_rmt_EndD3D11Sample();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if RMT_USE_OPENGL
|
||||
extern "C" RMT_API void _rmt_EndOpenGLSample(void);
|
||||
struct rmt_EndOpenGLSampleOnScopeExit
|
||||
{
|
||||
~rmt_EndOpenGLSampleOnScopeExit()
|
||||
{
|
||||
_rmt_EndOpenGLSample();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if RMT_USE_METAL
|
||||
extern "C" RMT_API void _rmt_EndMetalSample(void);
|
||||
struct rmt_EndMetalSampleOnScopeExit
|
||||
{
|
||||
~rmt_EndMetalSampleOnScopeExit()
|
||||
{
|
||||
_rmt_EndMetalSample();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// Pairs a call to rmt_Begin<TYPE>Sample with its call to rmt_End<TYPE>Sample when leaving scope
|
||||
#define rmt_ScopedCPUSample(name, flags) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, rmt_BeginCPUSample(name, flags)); \
|
||||
RMT_OPTIONAL(RMT_ENABLED, rmt_EndCPUSampleOnScopeExit rmt_ScopedCPUSample##name);
|
||||
#define rmt_ScopedCUDASample(name, stream) \
|
||||
RMT_OPTIONAL(RMT_USE_CUDA, rmt_BeginCUDASample(name, stream)); \
|
||||
RMT_OPTIONAL(RMT_USE_CUDA, rmt_EndCUDASampleOnScopeExit rmt_ScopedCUDASample##name(stream));
|
||||
#define rmt_ScopedD3D11Sample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, rmt_BeginD3D11Sample(name)); \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, rmt_EndD3D11SampleOnScopeExit rmt_ScopedD3D11Sample##name);
|
||||
#define rmt_ScopedOpenGLSample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, rmt_BeginOpenGLSample(name)); \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, rmt_EndOpenGLSampleOnScopeExit rmt_ScopedOpenGLSample##name);
|
||||
#define rmt_ScopedMetalSample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, rmt_BeginMetalSample(name)); \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, rmt_EndMetalSampleOnScopeExit rmt_ScopedMetalSample##name);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
Private Interface - don't directly call these
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#if RMT_ENABLED
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
RMT_API rmtSettings* _rmt_Settings( void );
|
||||
RMT_API enum rmtError _rmt_CreateGlobalInstance(Remotery** remotery);
|
||||
RMT_API void _rmt_DestroyGlobalInstance(Remotery* remotery);
|
||||
RMT_API void _rmt_SetGlobalInstance(Remotery* remotery);
|
||||
RMT_API Remotery* _rmt_GetGlobalInstance(void);
|
||||
RMT_API void _rmt_SetCurrentThreadName(rmtPStr thread_name);
|
||||
RMT_API void _rmt_LogText(rmtPStr text);
|
||||
RMT_API void _rmt_BeginCPUSample(rmtPStr name, rmtU32 flags, rmtU32* hash_cache);
|
||||
RMT_API void _rmt_EndCPUSample(void);
|
||||
|
||||
#if RMT_USE_CUDA
|
||||
RMT_API void _rmt_BindCUDA(const rmtCUDABind* bind);
|
||||
RMT_API void _rmt_BeginCUDASample(rmtPStr name, rmtU32* hash_cache, void* stream);
|
||||
RMT_API void _rmt_EndCUDASample(void* stream);
|
||||
#endif
|
||||
|
||||
#if RMT_USE_D3D11
|
||||
RMT_API void _rmt_BindD3D11(void* device, void* context);
|
||||
RMT_API void _rmt_UnbindD3D11(void);
|
||||
RMT_API void _rmt_BeginD3D11Sample(rmtPStr name, rmtU32* hash_cache);
|
||||
RMT_API void _rmt_EndD3D11Sample(void);
|
||||
#endif
|
||||
|
||||
#if RMT_USE_OPENGL
|
||||
RMT_API void _rmt_BindOpenGL();
|
||||
RMT_API void _rmt_UnbindOpenGL(void);
|
||||
RMT_API void _rmt_BeginOpenGLSample(rmtPStr name, rmtU32* hash_cache);
|
||||
RMT_API void _rmt_EndOpenGLSample(void);
|
||||
#endif
|
||||
|
||||
#if RMT_USE_METAL
|
||||
RMT_API void _rmt_BeginMetalSample(rmtPStr name, rmtU32* hash_cache);
|
||||
RMT_API void _rmt_EndMetalSample(void);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#if RMT_USE_METAL
|
||||
#ifdef __OBJC__
|
||||
RMT_API void _rmt_BindMetal(id command_buffer);
|
||||
RMT_API void _rmt_UnbindMetal();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // RMT_ENABLED
|
||||
|
||||
|
||||
#endif
|
|
@ -1,59 +0,0 @@
|
|||
//
|
||||
// Copyright 2014-2018 Celtoys Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <Foundation/NSThread.h>
|
||||
#include <Foundation/NSDictionary.h>
|
||||
#include <Foundation/NSString.h>
|
||||
|
||||
#import <Metal/Metal.h>
|
||||
|
||||
// Store command buffer in thread-local so that each thread can point to its own
|
||||
static void SetCommandBuffer(id command_buffer)
|
||||
{
|
||||
NSMutableDictionary* thread_data = [[NSThread currentThread] threadDictionary];
|
||||
thread_data[@"rmtMTLCommandBuffer"] = command_buffer;
|
||||
}
|
||||
|
||||
static id GetCommandBuffer()
|
||||
{
|
||||
NSMutableDictionary* thread_data = [[NSThread currentThread] threadDictionary];
|
||||
return thread_data[@"rmtMTLCommandBuffer"];
|
||||
}
|
||||
|
||||
extern "C" void _rmt_BindMetal(id command_buffer)
|
||||
{
|
||||
SetCommandBuffer(command_buffer);
|
||||
}
|
||||
|
||||
extern "C" void _rmt_UnbindMetal()
|
||||
{
|
||||
SetCommandBuffer(0);
|
||||
}
|
||||
|
||||
// Needs to be in the same lib for this to work
|
||||
extern "C" unsigned long long rmtMetal_usGetTime();
|
||||
|
||||
static void SetTimestamp(void* data)
|
||||
{
|
||||
*((unsigned long long*)data) = rmtMetal_usGetTime();
|
||||
}
|
||||
|
||||
extern "C" void rmtMetal_MeasureCommandBuffer(unsigned long long* out_start, unsigned long long* out_end, unsigned int* out_ready)
|
||||
{
|
||||
id command_buffer = GetCommandBuffer();
|
||||
[command_buffer addScheduledHandler:^(id <MTLCommandBuffer>){ SetTimestamp(out_start); }];
|
||||
[command_buffer addCompletedHandler:^(id <MTLCommandBuffer>){ SetTimestamp(out_end); *out_ready = 1; }];
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
Remotery
|
||||
--------
|
||||
|
||||
[![Build Status](https://travis-ci.org/Celtoys/Remotery.svg?branch=master)](https://travis-ci.org/Celtoys/Remotery)
|
||||
[![Build status](https://ci.appveyor.com/api/projects/status/d1o8620mws9ihbsd?svg=true)](https://ci.appveyor.com/project/Celtoys/remotery)
|
||||
|
||||
A realtime CPU/GPU profiler hosted in a single C file with a viewer that runs in a web browser.
|
||||
|
||||
![screenshot](screenshot.png?raw=true)
|
||||
|
||||
Supported Platforms:
|
||||
|
||||
* Windows
|
||||
* Windows UWP (Hololens)
|
||||
* Linux
|
||||
* OSX
|
||||
* iOS
|
||||
* Android
|
||||
* XBox One
|
||||
* FreeBSD
|
||||
|
||||
Supported GPU Profiling APIS:
|
||||
|
||||
* D3D 11
|
||||
* OpenGL
|
||||
* CUDA
|
||||
* Metal
|
||||
|
||||
Features:
|
||||
|
||||
* Lightweight instrumentation of multiple threads running on the CPU.
|
||||
* Web viewer that runs in Chrome, Firefox and Safari. Custom WebSockets server
|
||||
transmits sample data to the browser on a latent thread.
|
||||
* Profiles itself and shows how it's performing in the viewer.
|
||||
* Console output for logging text.
|
||||
* Console input for sending commands to your game.
|
||||
|
||||
|
||||
Compiling
|
||||
---------
|
||||
|
||||
* Windows (MSVC) - add lib/Remotery.c and lib/Remotery.h to your program. Set include
|
||||
directories to add Remotery/lib path. The required library ws2_32.lib should be picked
|
||||
up through the use of the #pragma comment(lib, "ws2_32.lib") directive in Remotery.c.
|
||||
|
||||
* Mac OS X (XCode) - simply add lib/Remotery.c, lib/Remotery.h and lib/Remotery.mm to your program.
|
||||
|
||||
* Linux (GCC) - add the source in lib folder. Compilation of the code requires -pthreads for
|
||||
library linkage. For example to compile the same run: cc lib/Remotery.c sample/sample.c
|
||||
-I lib -pthread -lm
|
||||
|
||||
* FreeBSD - the easiest way is to take a look at the official port
|
||||
([devel/remotery](https://www.freshports.org/devel/remotery/)) and modify the port's
|
||||
Makefile if needed. There is also a package available via `pkg install remotery`.
|
||||
|
||||
You can define some extra macros to modify what features are compiled into Remotery:
|
||||
|
||||
Macro Default Description
|
||||
|
||||
RMT_ENABLED 1 Disable this to not include any bits of Remotery in your build
|
||||
RMT_USE_TINYCRT 0 Used by the Celtoys TinyCRT library (not released yet)
|
||||
RMT_USE_CUDA 0 Assuming CUDA headers/libs are setup, allow CUDA profiling
|
||||
RMT_USE_D3D11 0 Assuming Direct3D 11 headers/libs are setup, allow D3D11 GPU profiling
|
||||
RMT_USE_OPENGL 0 Allow OpenGL GPU profiling (dynamically links OpenGL libraries on available platforms)
|
||||
RMT_USE_METAL 0 Allow Metal profiling of command buffers
|
||||
|
||||
|
||||
Basic Use
|
||||
---------
|
||||
|
||||
See the sample directory for further examples. A quick example:
|
||||
|
||||
int main()
|
||||
{
|
||||
// Create the main instance of Remotery.
|
||||
// You need only do this once per program.
|
||||
Remotery* rmt;
|
||||
rmt_CreateGlobalInstance(&rmt);
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginCPUSample(LogText, 0);
|
||||
rmt_LogText("Time me, please!");
|
||||
rmt_EndCPUSample();
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedCPUSample(LogText, 0);
|
||||
rmt_LogText("Time me, too!");
|
||||
}
|
||||
|
||||
// Destroy the main instance of Remotery.
|
||||
rmt_DestroyGlobalInstance(rmt);
|
||||
}
|
||||
|
||||
|
||||
Running the Viewer
|
||||
------------------
|
||||
|
||||
Double-click or launch `vis/index.html` from the browser.
|
||||
|
||||
|
||||
Sampling CUDA GPU activity
|
||||
--------------------------
|
||||
|
||||
Remotery allows for profiling multiple threads of CUDA execution using different asynchronous streams
|
||||
that must all share the same context. After initialising both Remotery and CUDA you need to bind the
|
||||
two together using the call:
|
||||
|
||||
rmtCUDABind bind;
|
||||
bind.context = m_Context;
|
||||
bind.CtxSetCurrent = &cuCtxSetCurrent;
|
||||
bind.CtxGetCurrent = &cuCtxGetCurrent;
|
||||
bind.EventCreate = &cuEventCreate;
|
||||
bind.EventDestroy = &cuEventDestroy;
|
||||
bind.EventRecord = &cuEventRecord;
|
||||
bind.EventQuery = &cuEventQuery;
|
||||
bind.EventElapsedTime = &cuEventElapsedTime;
|
||||
rmt_BindCUDA(&bind);
|
||||
|
||||
Explicitly pointing to the CUDA interface allows Remotery to be included anywhere in your project without
|
||||
need for you to link with the required CUDA libraries. After the bind completes you can safely sample any
|
||||
CUDA activity:
|
||||
|
||||
CUstream stream;
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginCUDASample(UnscopedSample, stream);
|
||||
// ... CUDA code ...
|
||||
rmt_EndCUDASample(stream);
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedCUDASample(ScopedSample, stream);
|
||||
// ... CUDA code ...
|
||||
}
|
||||
|
||||
Remotery supports only one context for all threads and will use cuCtxGetCurrent and cuCtxSetCurrent to
|
||||
ensure the current thread has the context you specify in rmtCUDABind.context.
|
||||
|
||||
|
||||
Sampling Direct3D 11 GPU activity
|
||||
---------------------------------
|
||||
|
||||
Remotery allows sampling of D3D11 GPU activity on multiple devices on multiple threads. After initialising Remotery, you need to bind it to D3D11 with a single call from the thread that owns the device context:
|
||||
|
||||
// Parameters are ID3D11Device* and ID3D11DeviceContext*
|
||||
rmt_BindD3D11(d3d11_device, d3d11_context);
|
||||
|
||||
Sampling is then a simple case of:
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginD3D11Sample(UnscopedSample);
|
||||
// ... D3D code ...
|
||||
rmt_EndD3D11Sample();
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedD3D11Sample(ScopedSample);
|
||||
// ... D3D code ...
|
||||
}
|
||||
|
||||
Subsequent sampling calls from the same thread will use that device/context combination. When you shutdown your D3D11 device and context, ensure you notify Remotery before shutting down Remotery itself:
|
||||
|
||||
rmt_UnbindD3D11();
|
||||
|
||||
|
||||
Sampling OpenGL GPU activity
|
||||
----------------------------
|
||||
|
||||
Remotery allows sampling of GPU activity on your main OpenGL context. After initialising Remotery, you need
|
||||
to bind it to OpenGL with the single call:
|
||||
|
||||
rmt_BindOpenGL();
|
||||
|
||||
Sampling is then a simple case of:
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginOpenGLSample(UnscopedSample);
|
||||
// ... OpenGL code ...
|
||||
rmt_EndOpenGLSample();
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedOpenGLSample(ScopedSample);
|
||||
// ... OpenGL code ...
|
||||
}
|
||||
|
||||
Support for multiple contexts can be added pretty easily if there is demand for the feature. When you shutdown
|
||||
your OpenGL device and context, ensure you notify Remotery before shutting down Remotery itself:
|
||||
|
||||
rmt_UnbindOpenGL();
|
||||
|
||||
|
||||
Sampling Metal GPU activity
|
||||
---------------------------
|
||||
|
||||
Remotery can sample Metal command buffers issued to the GPU from multiple threads. As the Metal API does not
|
||||
support finer grained profiling, samples will return only the timing of the bound command buffer, irrespective
|
||||
of how many you issue. As such, make sure you bind and sample the command buffer for each call site:
|
||||
|
||||
rmt_BindMetal(mtl_command_buffer);
|
||||
rmt_ScopedMetalSample(command_buffer_name);
|
||||
|
||||
The C API supports begin/end also:
|
||||
|
||||
rmt_BindMetal(mtl_command_buffer);
|
||||
rmt_BeginMetalSample(command_buffer_name);
|
||||
...
|
||||
rmt_EndMetalSample();
|
||||
|
||||
|
||||
Applying Configuration Settings
|
||||
-------------------------------
|
||||
|
||||
Before creating your Remotery instance, you can configure its behaviour by retrieving its settings object:
|
||||
|
||||
rmtSettings* settings = rmt_Settings();
|
||||
|
||||
Some important settings are:
|
||||
|
||||
// Redirect any Remotery allocations to your own malloc/free, with an additional context pointer
|
||||
// that gets passed to your callbacks.
|
||||
settings->malloc;
|
||||
settings->free;
|
||||
settings->mm_context;
|
||||
|
||||
// Specify an input handler that receives text input from the Remotery console, with an additional
|
||||
// context pointer that gets passed to your callback.
|
||||
// The handler will be called from the Remotery thread so synchronization with a mutex or atomics
|
||||
// might be needed to avoid race conditions with your threads.
|
||||
settings->input_handler;
|
||||
settings->input_handler_context;
|
|
@ -1,11 +0,0 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"typescript.tsdk": "C:\\Program Files (x86)\\Microsoft SDKs\\TypeScript\\2.0\\",
|
||||
"editor.fontSize": 13,
|
||||
"editor.lineHeight": 15,
|
||||
"editor.autoClosingBrackets": false,
|
||||
"editor.renderWhitespace": "all",
|
||||
"editor.quickSuggestions": true, // This disables string suggestions but ALSO disables local variables so keep it on
|
||||
"editor.wordBasedSuggestions": false, // Disable world-based suggestions being annoying in string editing (still some active)
|
||||
"editor.snippetSuggestions": "none" // This combined with above seems to completely eliminate string suggestions
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "0.1.0",
|
||||
"command": "tsc",
|
||||
"isShellCommand": true,
|
||||
"args": ["-p", "."],
|
||||
"showOutput": "silent",
|
||||
"problemMatcher": "$tsc"
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
|
||||
class DOMEvent
|
||||
{
|
||||
// Trigger for the DOM event
|
||||
Trigger: HTMLElement | Window;
|
||||
|
||||
// Event name
|
||||
EventName: string;
|
||||
|
||||
constructor(trigger: HTMLElement | Window, event_name: string)
|
||||
{
|
||||
this.Trigger = trigger;
|
||||
this.EventName = event_name;
|
||||
}
|
||||
|
||||
Subscribe(listener: EventListener) : void
|
||||
{
|
||||
this.Trigger.addEventListener(this.EventName, listener, false);
|
||||
}
|
||||
|
||||
Unsubscribe(listener: EventListener) : void
|
||||
{
|
||||
this.Trigger.removeEventListener(this.EventName, listener, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace DOM
|
||||
{
|
||||
export namespace Event
|
||||
{
|
||||
// Retrieves the event from the first parameter passed into an HTML event
|
||||
export function Get(event: MouseEvent) : MouseEvent
|
||||
{
|
||||
// Internet explorer doesn't pass the event
|
||||
return <MouseEvent>window.event || event;
|
||||
}
|
||||
|
||||
// Stops events bubbling up to parent event handlers
|
||||
export function StopPropagation(event: Event)
|
||||
{
|
||||
if (event)
|
||||
{
|
||||
event.cancelBubble = true;
|
||||
if (event.stopPropagation)
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop default action for event
|
||||
export function StopDefaultAction(event: Event)
|
||||
{
|
||||
if (event && event.preventDefault)
|
||||
event.preventDefault();
|
||||
else if (window.event && window.event.returnValue)
|
||||
window.event.returnValue = false;
|
||||
}
|
||||
|
||||
// Get the position of the mouse cursor, page relative
|
||||
export function GetMousePosition(event: MouseEvent) : int2
|
||||
{
|
||||
let e = Get(event);
|
||||
let p = new int2();
|
||||
if (e.pageX || e.pageY)
|
||||
{
|
||||
p.x = e.pageX;
|
||||
p.y = e.pageY;
|
||||
}
|
||||
else if (event.clientX || event.clientY)
|
||||
{
|
||||
p.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
|
||||
p.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}
|
||||
//export namespace Node
|
||||
{
|
||||
// Append an arbitrary block of HTML to an existing element
|
||||
/*export function AppendHTML(node: HTMLElement, html: string) : HTMLElement
|
||||
{
|
||||
var child = CreateHTML(html);
|
||||
node.appendChild(child);
|
||||
return child;
|
||||
}*/
|
||||
|
||||
// Append a div that clears the float style
|
||||
/*export function AppendClearFloat(node: HTMLElement) : HTMLElement
|
||||
{
|
||||
var child = document.createElement("div");
|
||||
child.style.clear = "both";
|
||||
node.appendChild(child);
|
||||
return child;
|
||||
}*/
|
||||
}
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
|
||||
namespace DOM
|
||||
{
|
||||
export class Node
|
||||
{
|
||||
// DOM element this object controls
|
||||
Element: HTMLElement;
|
||||
|
||||
// Optional DOM event handlers
|
||||
private _MouseDownEvent: DOMEvent;
|
||||
private _MouseUpEvent: DOMEvent;
|
||||
private _MouseMoveEvent: DOMEvent;
|
||||
private _ResizeEvent: DOMEvent;
|
||||
private _TouchStartEvent: DOMEvent;
|
||||
private _TouchEndEvent: DOMEvent;
|
||||
private _TouchCancelEvent: DOMEvent;
|
||||
private _TouchMoveEvent: DOMEvent;
|
||||
|
||||
|
||||
// ----- Constructor ---------------------------------------------------------------
|
||||
|
||||
|
||||
constructor(parameter: string | Element | Document | EventTarget)
|
||||
{
|
||||
// Take control of DOM objects
|
||||
if (parameter instanceof Element)
|
||||
{
|
||||
this.Element = <HTMLElement>parameter;
|
||||
}
|
||||
else if (parameter instanceof Document)
|
||||
{
|
||||
this.Element = <HTMLElement>parameter.documentElement;
|
||||
}
|
||||
else if (parameter instanceof EventTarget)
|
||||
{
|
||||
this.Element = <HTMLElement>parameter;
|
||||
}
|
||||
else if (typeof parameter === "string")
|
||||
{
|
||||
// Create a node from the provided HTML
|
||||
this.CreateFromHTML(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----- Properties ----------------------------------------------------------------
|
||||
|
||||
|
||||
// Absolute position of a HTML element on the page
|
||||
get AbsolutePosition() : int2
|
||||
{
|
||||
// Recurse up through parents, summing offsets from their parents
|
||||
let pos = new int2();
|
||||
for (let node = this.Element; node != null; node = <HTMLElement>node.offsetParent)
|
||||
{
|
||||
pos.x += node.offsetLeft;
|
||||
pos.y += node.offsetTop;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
set Position(position: int2)
|
||||
{
|
||||
this.Element.style.left = position.x.toString() + "px";
|
||||
this.Element.style.top = position.y.toString() + "px";
|
||||
}
|
||||
|
||||
// HTML element size, including borders and padding
|
||||
get Size() : int2
|
||||
{
|
||||
return new int2(this.Element.offsetWidth, this.Element.offsetHeight);
|
||||
}
|
||||
set Size(size: int2)
|
||||
{
|
||||
this.Element.style.width = size.x.toString() + "px";
|
||||
this.Element.style.height = size.y.toString() + "px";
|
||||
}
|
||||
|
||||
// Rendering z-index, applied through CSS
|
||||
get ZIndex() : number
|
||||
{
|
||||
if (this.Element.style.zIndex.length)
|
||||
return parseInt(this.Element.style.zIndex);
|
||||
return null;
|
||||
}
|
||||
set ZIndex(z_index: number)
|
||||
{
|
||||
this.Element.style.zIndex = z_index.toString();
|
||||
}
|
||||
|
||||
// Set HTML element opacity through CSS style
|
||||
set Opacity(value: number)
|
||||
{
|
||||
this.Element.style.opacity = value.toString();
|
||||
}
|
||||
|
||||
// Set HTML element colour through CSS style
|
||||
set Colour(colour: string)
|
||||
{
|
||||
this.Element.style.color = colour;
|
||||
}
|
||||
|
||||
set Cursor(cursor: string)
|
||||
{
|
||||
this.Element.style.cursor = cursor;
|
||||
}
|
||||
|
||||
// Return the parent HTML node
|
||||
get Parent() : Node
|
||||
{
|
||||
if (this.Element.parentElement)
|
||||
return new Node(this.Element.parentElement);
|
||||
return null;
|
||||
}
|
||||
get ParentElement() : HTMLElement
|
||||
{
|
||||
return this.Element.parentElement;
|
||||
}
|
||||
|
||||
|
||||
// ----- Methods -------------------------------------------------------------------
|
||||
|
||||
|
||||
// Check to see if a HTML element contains a class
|
||||
HasClass(class_name: string) : boolean
|
||||
{
|
||||
let regexp = new RegExp("\\b" + class_name + "\\b");
|
||||
return regexp.test(this.Element.className);
|
||||
}
|
||||
|
||||
// Remove a CSS class from a HTML element
|
||||
RemoveClass(class_name: string)
|
||||
{
|
||||
let regexp = new RegExp("\\b" + class_name + "\\b");
|
||||
this.Element.className = this.Element.className.replace(regexp, "");
|
||||
}
|
||||
|
||||
// Add a CSS class to a HTML element, specified last
|
||||
AddClass(class_name: string)
|
||||
{
|
||||
if (!this.HasClass(class_name))
|
||||
this.Element.className += " " + class_name;
|
||||
}
|
||||
|
||||
Find(filter: string) : Node
|
||||
{
|
||||
var element = this.Element.querySelector(filter);
|
||||
if (element)
|
||||
return new DOM.Node(element);
|
||||
return null;
|
||||
}
|
||||
|
||||
Append(node: Node)
|
||||
{
|
||||
this.Element.appendChild(node.Element);
|
||||
}
|
||||
|
||||
Detach()
|
||||
{
|
||||
if (this.Element.parentNode)
|
||||
this.Element.parentNode.removeChild(this.Element);
|
||||
}
|
||||
|
||||
Contains(node: Node) : boolean
|
||||
{
|
||||
while (node.Element != null && node.Element != this.Element)
|
||||
node = node.Parent;
|
||||
return node != null;
|
||||
}
|
||||
|
||||
SetText(text: string)
|
||||
{
|
||||
this.Element.textContent = text;
|
||||
}
|
||||
|
||||
// Create the HTML elements specified in the text parameter
|
||||
// Assumes there is only one root node in the text
|
||||
private CreateFromHTML(html: string)
|
||||
{
|
||||
// Prevent creation of superfluous text nodes
|
||||
html = html.trim();
|
||||
|
||||
// Create a temporary template to apply the HTML to
|
||||
let template = document.createElement("template");
|
||||
template.innerHTML = html;
|
||||
this.Element = <HTMLElement>template.content.firstElementChild;
|
||||
}
|
||||
|
||||
|
||||
// ----- Events --------------------------------------------------------------------
|
||||
|
||||
|
||||
// All event objects get created on-demand
|
||||
get MouseDownEvent() : DOMEvent
|
||||
{
|
||||
this._MouseDownEvent = this._MouseDownEvent || new DOMEvent(this.Element, "mousedown");
|
||||
return this._MouseDownEvent;
|
||||
}
|
||||
get MouseUpEvent() : DOMEvent
|
||||
{
|
||||
this._MouseUpEvent = this._MouseUpEvent || new DOMEvent(this.Element, "mouseup");
|
||||
return this._MouseUpEvent;
|
||||
}
|
||||
get MouseMoveEvent() : DOMEvent
|
||||
{
|
||||
this._MouseMoveEvent = this._MouseMoveEvent || new DOMEvent(this.Element, "mousemove");
|
||||
return this._MouseMoveEvent;
|
||||
}
|
||||
get ResizeEvent() : DOMEvent
|
||||
{
|
||||
this._ResizeEvent = this._ResizeEvent || new DOMEvent(window, "resize");
|
||||
return this._ResizeEvent;
|
||||
}
|
||||
get TouchStartEvent() : DOMEvent
|
||||
{
|
||||
this._TouchStartEvent = this._TouchStartEvent || new DOMEvent(this.Element, "touchstart");
|
||||
return this._TouchStartEvent;
|
||||
}
|
||||
get TouchEndEvent() : DOMEvent
|
||||
{
|
||||
this._TouchEndEvent = this._TouchEndEvent || new DOMEvent(this.Element, "touchend");
|
||||
return this._TouchEndEvent;
|
||||
}
|
||||
get TouchCancelEvent() : DOMEvent
|
||||
{
|
||||
this._TouchCancelEvent = this._TouchCancelEvent || new DOMEvent(this.Element, "touchcancel");
|
||||
return this._TouchCancelEvent;
|
||||
}
|
||||
get TouchMoveEvent() : DOMEvent
|
||||
{
|
||||
this._TouchMoveEvent = this._TouchMoveEvent || new DOMEvent(this.Element, "touchmove");
|
||||
return this._TouchMoveEvent;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function $(parameter: string | Element | Document | EventTarget)
|
||||
{
|
||||
if (typeof parameter == "string")
|
||||
return new DOM.Node(document.querySelector(parameter));
|
||||
|
||||
return new DOM.Node(parameter);
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
namespace Hash
|
||||
{
|
||||
export function Wang_U32(key: number) : number
|
||||
{
|
||||
key += ~(key << 15);
|
||||
key ^= (key >> 10);
|
||||
key += (key << 3);
|
||||
key ^= (key >> 6);
|
||||
key += ~(key << 11);
|
||||
key ^= (key >> 16);
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
export function Combine_U32(hash_a: number, hash_b: number) : number
|
||||
{
|
||||
let random_bits = 0x9E3779B9;
|
||||
hash_a ^= hash_b + random_bits + (hash_a << 6) + (hash_a >> 2);
|
||||
return hash_a;
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
|
||||
// Internal operations of int2 can assume inputs are already integer and avoid rounding
|
||||
enum int2Round
|
||||
{
|
||||
Do,
|
||||
Dont
|
||||
}
|
||||
|
||||
|
||||
class int2
|
||||
{
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
static readonly Zero = new int2(0, 0);
|
||||
static readonly One = new int2(1, 1);
|
||||
|
||||
constructor(x: number = 0, y: number = x, round: int2Round = int2Round.Do)
|
||||
{
|
||||
if (round == int2Round.Do)
|
||||
{
|
||||
this.x = Math.round(x);
|
||||
this.y = Math.round(y);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
Copy() : int2
|
||||
{
|
||||
return new int2(this.x, this.y, int2Round.Dont);
|
||||
}
|
||||
|
||||
static Add(a: int2, b: int2) : int2
|
||||
{
|
||||
return new int2(a.x + b.x, a.y + b.y, int2Round.Dont);
|
||||
}
|
||||
|
||||
static Sub(a: int2, b: int2) : int2
|
||||
{
|
||||
return new int2(a.x - b.x, a.y - b.y, int2Round.Dont);
|
||||
}
|
||||
|
||||
static Mul(a: int2, b: int2) : int2
|
||||
{
|
||||
return new int2(a.x * b.x, a.y * b.y, int2Round.Dont);
|
||||
}
|
||||
|
||||
static Min(a: int2, b: int2) : int2
|
||||
{
|
||||
return new int2(Math.min(a.x, b.x), Math.min(a.y, b.y), int2Round.Dont);
|
||||
}
|
||||
|
||||
static Max(a: int2, b: int2) : int2
|
||||
{
|
||||
return new int2(Math.max(a.x, b.x), Math.max(a.y, b.y), int2Round.Dont);
|
||||
}
|
||||
|
||||
static Min0(a: int2) : int2
|
||||
{
|
||||
return new int2(Math.min(a.x, 0), Math.min(a.y, 0), int2Round.Dont);
|
||||
}
|
||||
|
||||
static Max0(a: int2) : int2
|
||||
{
|
||||
return new int2(Math.max(a.x, 0), Math.max(a.y, 0), int2Round.Dont);
|
||||
}
|
||||
|
||||
static Neg(a: int2) : int2
|
||||
{
|
||||
return new int2(-a.x, -a.y, int2Round.Dont);
|
||||
}
|
||||
|
||||
static Abs(a: int2) : int2
|
||||
{
|
||||
return new int2(Math.abs(a.x), Math.abs(a.y), int2Round.Dont);
|
||||
}
|
||||
|
||||
static Equal(a: int2, b: int2) : boolean
|
||||
{
|
||||
if (a == null || b == null)
|
||||
return false;
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AABB
|
||||
{
|
||||
min: int2;
|
||||
max: int2;
|
||||
|
||||
constructor(min: int2, max: int2)
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
Expand(e: number) : void
|
||||
{
|
||||
let ev = new int2(e);
|
||||
this.min = int2.Sub(this.min, ev);
|
||||
this.max = int2.Add(this.max, ev);
|
||||
}
|
||||
|
||||
static Intersect(a: AABB, b: AABB) : boolean
|
||||
{
|
||||
return a.min.x < b.max.x && a.min.y < b.max.y && b.min.x < a.max.x && b.min.y < a.max.y;
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
|
||||
//
|
||||
// TODO:
|
||||
//
|
||||
// * Show windows with animation?
|
||||
// * Make root window that's embedded in the browser.
|
||||
// * When adding a control, give option to add as shown/hidden.
|
||||
// * Move all WindowManager functionality into Window and apply it to Controls.
|
||||
//
|
||||
|
||||
function TestAll() : WM.Container
|
||||
{
|
||||
let Container = new WM.Container(new int2(10, 10), new int2(1000, 800));
|
||||
Container.Show();
|
||||
|
||||
let WindowA = new WM.Window("Window A", new int2(10, 10), new int2(200, 200));
|
||||
WindowA.Title = "Window A Changed";
|
||||
Container.Add(WindowA);
|
||||
|
||||
WindowA.Add(new WM.Window("SubWindow 0 A", new int2(10, 10), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 B", new int2(20, 20), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 C", new int2(30, 30), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 D", new int2(40, 40), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 E", new int2(50, 50), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 F", new int2(60, 60), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 G", new int2(70, 70), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 H", new int2(80, 80), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 I", new int2(90, 90), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 J", new int2(100, 100), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 K", new int2(110, 110), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 L", new int2(120, 120), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 M", new int2(130, 130), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 N", new int2(140, 140), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 O", new int2(150, 150), new int2(200, 200)));
|
||||
|
||||
Container.Add(new WM.Window("Window B", new int2(220, 10), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window C", new int2(430, 10), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window D", new int2(640, 10), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window E", new int2(10, 220), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window F", new int2(220, 220), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window G", new int2(430, 220), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window H", new int2(640, 220), new int2(200, 200)));
|
||||
|
||||
let WindowI = new WM.Window("Window I", new int2(500, 400), new int2(300, 300));
|
||||
Container.Add(WindowI);
|
||||
WindowI.Add(new WM.Window("SubWindow 1 A", new int2(10, 10), new int2(289, 289)));
|
||||
WindowI.Add(new WM.Window("SubWindow 1 B", new int2(20, 20), new int2(289, 289)));
|
||||
WindowI.Add(new WM.Window("SubWindow 1 C", new int2(30, 30), new int2(289, 289)));
|
||||
WindowI.Add(new WM.Window("SubWindow 1 D", new int2(40, 40), new int2(289, 289)));
|
||||
WindowI.Add(new WM.Window("SubWindow 1 E", new int2(50, 50), new int2(289, 289)));
|
||||
|
||||
return Container;
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
export class Container extends Control
|
||||
{
|
||||
static TemplateHTML = "<div class='Container'></div>";
|
||||
|
||||
static SnapBorderSize = 5;
|
||||
|
||||
// List of controls contained by the window, in z-order
|
||||
Controls: Control[] = [];
|
||||
|
||||
// Connectivity graph for all controls in the container, allowing auto-anchor
|
||||
ControlGraph: ControlGraph = new ControlGraph();
|
||||
|
||||
// Sizing simulation for controls on each axis
|
||||
protected ControlSizerX: ControlSizer = new ControlSizer();
|
||||
protected ControlSizerY: ControlSizer = new ControlSizer();
|
||||
|
||||
constructor(position: int2, size: int2, node?: DOM.Node)
|
||||
{
|
||||
super(node ? node : new DOM.Node(Container.TemplateHTML), position, size);
|
||||
}
|
||||
|
||||
Add(control: Control) : Control
|
||||
{
|
||||
this.Controls.push(control);
|
||||
control.ParentContainer = this;
|
||||
control.Show();
|
||||
return control;
|
||||
}
|
||||
|
||||
Remove(control: Control)
|
||||
{
|
||||
control.Hide();
|
||||
|
||||
let index = this.Controls.indexOf(control);
|
||||
this.Controls.splice(index, 1);
|
||||
|
||||
control.ParentContainer = null;
|
||||
}
|
||||
|
||||
private UpdateZIndices()
|
||||
{
|
||||
// ZINDEX needs to be relative to parent!
|
||||
|
||||
// Set a CSS z-index for each visible control from the bottom-up
|
||||
for (let i = 0; i < this.Controls.length; i++)
|
||||
{
|
||||
let control = this.Controls[i];
|
||||
if (!control.Visible)
|
||||
continue;
|
||||
|
||||
// Ensure there's space between each window for the elements inside to be sorted
|
||||
let z = (i + 1) * 10;
|
||||
control.ZIndex = z;
|
||||
}
|
||||
}
|
||||
|
||||
SetTopControl(control: Control) : void
|
||||
{
|
||||
// Push the control to the end of the control list
|
||||
let index = this.Controls.indexOf(control);
|
||||
if (index != -1)
|
||||
{
|
||||
this.Controls.splice(index, 1);
|
||||
this.Controls.push(control);
|
||||
|
||||
// Recalculate z-indices for visible sort
|
||||
this.UpdateZIndices();
|
||||
}
|
||||
}
|
||||
|
||||
SetBottomControl(control: Control) : void
|
||||
{
|
||||
// Push the control to the start of the control list
|
||||
let index = this.Controls.indexOf(control);
|
||||
if (index != -1)
|
||||
{
|
||||
this.Controls.splice(index, 1);
|
||||
this.Controls.unshift(control);
|
||||
|
||||
// Recalculate z-indices for visible sort
|
||||
this.UpdateZIndices();
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the node which all controls added to the container are parented to
|
||||
get ControlParentNode() : DOM.Node
|
||||
{
|
||||
return this.Node;
|
||||
}
|
||||
|
||||
protected SetSize(size: int2) : void
|
||||
{
|
||||
// Set size on super and notify child controls
|
||||
super.SetSize(size);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
function GenerateID(position: int2, size: int2) : number
|
||||
{
|
||||
// Use initial placement of the container as a unique ID generator
|
||||
let a = Hash.Combine_U32(Hash.Wang_U32(position.x), Hash.Wang_U32(position.y));
|
||||
let b = Hash.Combine_U32(Hash.Wang_U32(size.x), Hash.Wang_U32(size.y));
|
||||
return Hash.Combine_U32(a, b);
|
||||
}
|
||||
|
||||
|
||||
export class Control
|
||||
{
|
||||
// Unique ID within a container's control list
|
||||
ID: number;
|
||||
|
||||
// Main generated HTML node for the control
|
||||
Node: DOM.Node;
|
||||
|
||||
// Parent container for the control; can be null in the case of the browser body
|
||||
private _ParentContainer: Container;
|
||||
|
||||
// Rectangle coverage
|
||||
// Always ensure position/size are defined as they are required to calculate bottom-right
|
||||
private _Position = new int2(0);
|
||||
private _Size = new int2(0);
|
||||
private _BottomRight = new int2(0);
|
||||
|
||||
// Records current visibility state - not for external writes
|
||||
private _Visible: boolean = false;
|
||||
|
||||
|
||||
// ----- Constructor ---------------------------------------------------------------
|
||||
|
||||
|
||||
constructor(node: DOM.Node, position: int2, size: int2)
|
||||
{
|
||||
this.ID = GenerateID(position, size);
|
||||
this.Node = node;
|
||||
this.Position = position;
|
||||
this.Size = size;
|
||||
|
||||
this.Node.MouseDownEvent.Subscribe(this.OnMouseDown);
|
||||
}
|
||||
|
||||
|
||||
// ----- Public API Properties -----------------------------------------------------
|
||||
|
||||
|
||||
// Cached node position
|
||||
set Position(position: int2)
|
||||
{
|
||||
this._Position = position;
|
||||
this.Node.Position = position;
|
||||
this._BottomRight = int2.Add(this._Position, this._Size);
|
||||
}
|
||||
get Position() : int2
|
||||
{
|
||||
return this._Position;
|
||||
}
|
||||
|
||||
// Cached node size
|
||||
// Size set in alternate implementation because you can't access
|
||||
// properties from 'super'
|
||||
protected SetSize(size: int2) : void
|
||||
{
|
||||
this._Size = size;
|
||||
this.Node.Size = size;
|
||||
this._BottomRight = int2.Add(this._Position, this._Size);
|
||||
}
|
||||
set Size(size: int2)
|
||||
{
|
||||
this.SetSize(size);
|
||||
}
|
||||
get Size() : int2
|
||||
{
|
||||
return this._Size;
|
||||
}
|
||||
|
||||
// Alternative rectangle coverage access
|
||||
set TopLeft(tl: int2)
|
||||
{
|
||||
let old_br = this._BottomRight.Copy();
|
||||
this.Position = tl;
|
||||
this.Size = int2.Sub(old_br, this.Position);
|
||||
}
|
||||
get TopLeft() : int2
|
||||
{
|
||||
return this._Position;
|
||||
}
|
||||
set BottomRight(br: int2)
|
||||
{
|
||||
this.SetSize(int2.Sub(br, this._Position));
|
||||
}
|
||||
get BottomRight() : int2
|
||||
{
|
||||
return this._BottomRight;
|
||||
}
|
||||
|
||||
// Tells whether the control thinks it's visible or not
|
||||
get Visible() : boolean
|
||||
{
|
||||
return this._Visible;
|
||||
}
|
||||
|
||||
|
||||
// ----- Internal API Properties ---------------------------------------------------
|
||||
|
||||
|
||||
// Set/Get control z-index
|
||||
set ZIndex(z_index: number)
|
||||
{
|
||||
this.Node.ZIndex = z_index;
|
||||
}
|
||||
get ZIndex() : number
|
||||
{
|
||||
return this.Node.ZIndex;
|
||||
}
|
||||
|
||||
// Set/Get the parent container for this control
|
||||
set ParentContainer(parent_container: Container)
|
||||
{
|
||||
if (this._ParentContainer == null)
|
||||
$(document.body).ResizeEvent.Unsubscribe(this.OnParentResize);
|
||||
|
||||
this._ParentContainer = parent_container;
|
||||
|
||||
if (this._ParentContainer == null)
|
||||
$(document.body).ResizeEvent.Subscribe(this.OnParentResize);
|
||||
}
|
||||
get ParentContainer() : Container
|
||||
{
|
||||
return this._ParentContainer;
|
||||
}
|
||||
|
||||
// Returns a node within the parent container that's designated for adding controls
|
||||
// Or document.body if there is no parent
|
||||
protected get ParentNode() : DOM.Node
|
||||
{
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container == null)
|
||||
return $(document.body);
|
||||
return parent_container.ControlParentNode;
|
||||
}
|
||||
|
||||
|
||||
// ----- Public API Methods --------------------------------------------------------
|
||||
|
||||
|
||||
Show() : void
|
||||
{
|
||||
// Add to parent node if not already there
|
||||
if (this.Node.Parent == null)
|
||||
{
|
||||
this.ParentNode.Append(this.Node);
|
||||
this._Visible = true;
|
||||
}
|
||||
}
|
||||
Hide() : void
|
||||
{
|
||||
if (this.Node.Parent != null)
|
||||
{
|
||||
this.Node.Detach();
|
||||
this._Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
SendToTop() : void
|
||||
{
|
||||
if (this._ParentContainer)
|
||||
this._ParentContainer.SetTopControl(this);
|
||||
}
|
||||
|
||||
SendToBottom() : void
|
||||
{
|
||||
if (this._ParentContainer)
|
||||
this._ParentContainer.SetBottomControl(this);
|
||||
}
|
||||
|
||||
|
||||
// ----- Internal API Methods ------------------------------------------------------
|
||||
|
||||
|
||||
OnParentResize = () =>
|
||||
{
|
||||
// TODO: Snap on show?
|
||||
}
|
||||
|
||||
private OnMouseDown = (event: MouseEvent) =>
|
||||
{
|
||||
// Allow bubble-up for this event so that it filters through nested windows
|
||||
this.SendToTop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,273 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
export class ControlRef
|
||||
{
|
||||
// Primary sort key, to be combined with Side
|
||||
FromIndex: number;
|
||||
|
||||
// Cached control reference to save needing to lookup in the parent
|
||||
From: Control;
|
||||
|
||||
// Which side of the control the reference is on
|
||||
Side: Side;
|
||||
|
||||
// Control this references
|
||||
ToIndex: number;
|
||||
To: Control;
|
||||
|
||||
|
||||
constructor(from_index: number, from: Control, side: Side, to_index: number, to: Control)
|
||||
{
|
||||
this.FromIndex = from_index;
|
||||
this.From = from;
|
||||
this.Side = side;
|
||||
this.ToIndex = to_index;
|
||||
this.To = to;
|
||||
}
|
||||
|
||||
get SortIndex() : number
|
||||
{
|
||||
return this.FromIndex * 4 + this.Side;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ControlRefInfo
|
||||
{
|
||||
// Storing this allows the class to query graph properties without the extra parameter
|
||||
ParentGraph: ControlGraph;
|
||||
|
||||
// Cached control reference to save needing to lookup in the parent
|
||||
Control: Control;
|
||||
|
||||
// Side specified for debug
|
||||
Side: Side;
|
||||
|
||||
// Links in the control ref array
|
||||
StartRef: number;
|
||||
NbRefs: number;
|
||||
|
||||
constructor(parent_graph: ControlGraph, control: Control, side: Side)
|
||||
{
|
||||
this.ParentGraph = parent_graph;
|
||||
this.Control = control;
|
||||
this.Side = side;
|
||||
this.StartRef = -1;
|
||||
this.NbRefs = 0;
|
||||
}
|
||||
|
||||
References(control: Control) : boolean
|
||||
{
|
||||
for (let i = 0; i < this.NbRefs; i++)
|
||||
{
|
||||
if (this.ParentGraph.Refs[this.StartRef + i].To == control)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
GetControlRef(index: number) : ControlRef
|
||||
{
|
||||
if (index < this.NbRefs)
|
||||
return this.ParentGraph.Refs[this.StartRef + index];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
GetSide(side: Side) : ControlRefInfo
|
||||
{
|
||||
if (this.NbRefs == 0)
|
||||
return null;
|
||||
|
||||
let ref = this.ParentGraph.Refs[this.StartRef];
|
||||
return this.ParentGraph.RefInfos[ref.FromIndex * 4 + side];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ControlGraph
|
||||
{
|
||||
Refs: ControlRef[] = [];
|
||||
|
||||
RefInfos: ControlRefInfo[] = [];
|
||||
|
||||
Build(container: Container)
|
||||
{
|
||||
// Clear existing references
|
||||
this.Refs = [ ];
|
||||
this.RefInfos = [ ];
|
||||
|
||||
// Mark all controls as unvisited
|
||||
let control_visited: boolean[] = [];
|
||||
for (let i = 0; i < container.Controls.length; i++)
|
||||
control_visited.push(false);
|
||||
|
||||
// Build references for each container
|
||||
for (let i = 0; i < container.Controls.length; i++)
|
||||
{
|
||||
if (control_visited[i])
|
||||
continue;
|
||||
|
||||
let control = container.Controls[i];
|
||||
|
||||
// TODO: Exempt Rulers but allow Buttons? Or, exempt any control from auto snap/anchor?
|
||||
// This is technically a container graph right now
|
||||
if (!(control instanceof Container))
|
||||
continue;
|
||||
|
||||
this.BuildRefs(control, container, control_visited);
|
||||
}
|
||||
|
||||
// Sort control references, packing controls/sides next to each other
|
||||
this.Refs.sort((a: ControlRef, b: ControlRef) : number =>
|
||||
{
|
||||
return a.SortIndex - b.SortIndex;
|
||||
});
|
||||
|
||||
// Initialise the control ref info array
|
||||
for (let i = 0; i < container.Controls.length * 4; i++)
|
||||
{
|
||||
let control = container.Controls[i >> 2];
|
||||
this.RefInfos.push(new ControlRefInfo(this, control, i & 3));
|
||||
}
|
||||
|
||||
// Tell each control where its reference list starts and ends
|
||||
let last_sort_index = -1;
|
||||
for (let i = 0; i < this.Refs.length; i++)
|
||||
{
|
||||
let ref = this.Refs[i];
|
||||
let sort_index = ref.SortIndex;
|
||||
let ref_info = this.RefInfos[sort_index];
|
||||
|
||||
if (last_sort_index != sort_index)
|
||||
{
|
||||
ref_info.StartRef = i;
|
||||
last_sort_index = sort_index;
|
||||
}
|
||||
|
||||
ref_info.NbRefs++;
|
||||
}
|
||||
}
|
||||
|
||||
private BuildRefs(root_control: Control, container: Container, control_visited: boolean[])
|
||||
{
|
||||
// First control to visit is the root control
|
||||
let to_visit_controls: Control[] = [ root_control ];
|
||||
|
||||
// Loop through any controls left to visit
|
||||
for (let control_0 of to_visit_controls)
|
||||
{
|
||||
// It's possible for the same container to be pushed onto the to-visit list more than once
|
||||
let control_0_index = container.Controls.indexOf(control_0);
|
||||
if (control_visited[control_0_index])
|
||||
continue;
|
||||
control_visited[control_0_index] = true;
|
||||
|
||||
let tl_0 = control_0.TopLeft;
|
||||
let br_0 = control_0.BottomRight;
|
||||
|
||||
// Add references to the parent container
|
||||
let b = Container.SnapBorderSize;
|
||||
let s = container.ControlParentNode.Size;
|
||||
if (tl_0.x <= b)
|
||||
this.Refs.push(new ControlRef(control_0_index, control_0, Side.Left, -1, container));
|
||||
if (tl_0.y <= b)
|
||||
this.Refs.push(new ControlRef(control_0_index, control_0, Side.Top, -1, container));
|
||||
if (br_0.x >= s.x - b)
|
||||
this.Refs.push(new ControlRef(control_0_index, control_0, Side.Right, -1, container));
|
||||
if (br_0.y >= s.y - b)
|
||||
this.Refs.push(new ControlRef(control_0_index, control_0, Side.Bottom, -1, container));
|
||||
|
||||
// Check candidate controls for auto-anchor intersection
|
||||
for (let control_1 of container.Controls)
|
||||
{
|
||||
// If a control has been previous visited, no need to add forward links as back
|
||||
// links would have been added when it was visited
|
||||
let control_1_index = container.Controls.indexOf(control_1);
|
||||
if (control_visited[control_1_index])
|
||||
continue;
|
||||
|
||||
// TODO: Exempt Rulers but allow Buttons? Or, exempt any control from auto snap/anchor?
|
||||
if (!(control_1 instanceof Container))
|
||||
continue;
|
||||
|
||||
let tl_1 = control_1.TopLeft;
|
||||
let br_1 = control_1.BottomRight;
|
||||
|
||||
let side_0 = Side.None;
|
||||
let side_1 = Side.None;
|
||||
|
||||
// Check for vertical separating axis
|
||||
if (tl_1.y - br_0.y < 0 && tl_0.y - br_1.y < 0)
|
||||
{
|
||||
// Check left/right edge intersection
|
||||
if (Math.abs(tl_0.x - br_1.x) < b)
|
||||
{
|
||||
side_0 = Side.Left;
|
||||
side_1 = Side.Right;
|
||||
}
|
||||
if (Math.abs(br_0.x - tl_1.x) < b)
|
||||
{
|
||||
side_0 = Side.Right;
|
||||
side_1 = Side.Left;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for horizontal separating axis
|
||||
if (tl_1.x - br_0.x < 0 && tl_0.x - br_1.x < 0)
|
||||
{
|
||||
// Check top/bottom edge intersection
|
||||
if (Math.abs(tl_0.y - br_1.y) < b)
|
||||
{
|
||||
side_0 = Side.Top;
|
||||
side_1 = Side.Bottom;
|
||||
}
|
||||
if (Math.abs(br_0.y - tl_1.y) < b)
|
||||
{
|
||||
side_0 = Side.Bottom;
|
||||
side_1 = Side.Top;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate references for any intersection
|
||||
if (side_0 != Side.None)
|
||||
{
|
||||
this.Refs.push(new ControlRef(control_0_index, control_0, side_0, control_1_index, control_1));
|
||||
this.Refs.push(new ControlRef(control_1_index, control_1, side_1, control_0_index, control_0));
|
||||
to_visit_controls.push(control_1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugLog()
|
||||
{
|
||||
console.log("\n--- DebugLog --------------------------------");
|
||||
|
||||
let x = Side[Side.Top];
|
||||
for (let ref_info of this.RefInfos)
|
||||
{
|
||||
if (!(ref_info.Control instanceof Container))
|
||||
continue;
|
||||
if (ref_info.NbRefs == 0)
|
||||
continue;
|
||||
|
||||
let names = "";
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
let window = this.Refs[ref_info.StartRef + i].To as Window;
|
||||
names += window.Title + ", ";
|
||||
}
|
||||
|
||||
console.log((<Window>ref_info.Control).Title, Side[ref_info.Side] + ": ", names);
|
||||
}
|
||||
|
||||
/*for (let ref of this.Refs)
|
||||
{
|
||||
console.log((<Window>ref.From).Title, ref.Side, (<Window>ref.To).Title);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,515 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
class Span
|
||||
{
|
||||
// Only for DebugLog
|
||||
Title: string;
|
||||
|
||||
// Source control for copying simulation back
|
||||
Control: Control;
|
||||
|
||||
Min: number;
|
||||
Max: number;
|
||||
|
||||
RestSizeStrength: number;
|
||||
SizeStrength: number;
|
||||
|
||||
// Number of controls between this one and the side of the container
|
||||
SideDistance: number;
|
||||
}
|
||||
|
||||
class SizeConstraint
|
||||
{
|
||||
Span: Span;
|
||||
Size: number;
|
||||
}
|
||||
|
||||
class ContainerConstraint
|
||||
{
|
||||
Span: Span;
|
||||
Side: Side;
|
||||
Position: number;
|
||||
}
|
||||
|
||||
class BufferConstraint
|
||||
{
|
||||
Span0: Span;
|
||||
Span1: Span;
|
||||
Side: Side;
|
||||
}
|
||||
|
||||
class SnapConstraint
|
||||
{
|
||||
MinSpan: Span;
|
||||
MaxSpan: Span;
|
||||
}
|
||||
|
||||
// TODO: Need to unify snapped controls into one constraint instead of multiple
|
||||
export class ControlSizer
|
||||
{
|
||||
// Allow the sizer to work independently on horizontal/vertical axes
|
||||
MinSide: Side;
|
||||
MaxSide: Side;
|
||||
|
||||
ContainerRestSize: number;
|
||||
ContainerSize: number;
|
||||
|
||||
Spans: Span[] = [];
|
||||
|
||||
ContainerConstraints: ContainerConstraint[] = [];
|
||||
BufferConstraints: BufferConstraint[] = [];
|
||||
SizeConstraints: SizeConstraint[] = [];
|
||||
SnapConstraints: SnapConstraint[] = [];
|
||||
|
||||
Clear()
|
||||
{
|
||||
this.Spans = [];
|
||||
this.ContainerConstraints = [];
|
||||
this.BufferConstraints = [];
|
||||
this.SizeConstraints = [];
|
||||
this.SnapConstraints = [];
|
||||
}
|
||||
|
||||
Build(base_side: Side, container: Container, control_graph: ControlGraph)
|
||||
{
|
||||
this.MinSide = base_side;
|
||||
this.MaxSide = base_side + 1;
|
||||
|
||||
if (base_side == Side.Left)
|
||||
this.ContainerRestSize = container.ControlParentNode.Size.x;
|
||||
else
|
||||
this.ContainerRestSize = container.ControlParentNode.Size.y;
|
||||
|
||||
// Clear previous constraints
|
||||
this.Clear();
|
||||
|
||||
// Build the span list
|
||||
this.BuildSpans(container);
|
||||
|
||||
// Build constraints
|
||||
let min_controls: number[] = [];
|
||||
let max_controls: number[] = [];
|
||||
this.BuildContainerConstraints(container, control_graph, min_controls, max_controls);
|
||||
this.BuildBufferConstraints(container, control_graph);
|
||||
this.BuildSnapConstraints(container, control_graph);
|
||||
|
||||
this.SetInitialSizeStrengths(container, control_graph, min_controls, max_controls);
|
||||
}
|
||||
|
||||
ChangeSize(new_size: number, control_graph: ControlGraph)
|
||||
{
|
||||
// Update container constraints with new size
|
||||
this.ContainerSize = new_size;
|
||||
let half_delta_size = (this.ContainerRestSize - new_size) / 2;
|
||||
let min_offset = half_delta_size + Container.SnapBorderSize;
|
||||
let max_offset = this.ContainerRestSize - min_offset;
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
constraint.Position = min_offset;
|
||||
else
|
||||
constraint.Position = max_offset;
|
||||
}
|
||||
|
||||
// Relax
|
||||
for (let i = 0; i < 50; i++)
|
||||
{
|
||||
this.ApplySizeConstraints();
|
||||
this.ApplyMinimumSizeConstraints();
|
||||
this.ApplyBufferConstraints();
|
||||
|
||||
// Do this here before non-spring constraints
|
||||
this.IntegerRoundSpans();
|
||||
|
||||
this.ApplyContainerConstraints();
|
||||
|
||||
// HERE
|
||||
this.ReevaluateSizeStrengths(control_graph);
|
||||
}
|
||||
|
||||
// TODO: Finish with a snap! Can that be made into a constraint?
|
||||
// Problem is that multiple controls may be out of line
|
||||
this.ApplySnapConstraints();
|
||||
this.ApplyContainerConstraints();
|
||||
|
||||
// Copy simulation back to the controls
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
if (this.MinSide == Side.Left)
|
||||
{
|
||||
span.Control.Position = new int2(span.Min - half_delta_size, span.Control.Position.y);
|
||||
span.Control.Size = new int2(span.Max - span.Min, span.Control.Size.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
span.Control.Position = new int2(span.Control.Position.x, span.Min - half_delta_size);
|
||||
span.Control.Size = new int2(span.Control.Size.x, span.Max - span.Min);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildSpans(container: Container)
|
||||
{
|
||||
for (let control of container.Controls)
|
||||
{
|
||||
// Anything that's not a control (e.g. a Ruler) still needs an entry in the array, even if it's empty
|
||||
if (!(control instanceof Container))
|
||||
{
|
||||
this.Spans.push(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set initial parameters
|
||||
let span = new Span();
|
||||
span.Control = control;
|
||||
if (this.MinSide == Side.Left)
|
||||
{
|
||||
span.Min = control.TopLeft.x;
|
||||
span.Max = control.BottomRight.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
span.Min = control.TopLeft.y;
|
||||
span.Max = control.BottomRight.y;
|
||||
}
|
||||
span.SizeStrength = 1;
|
||||
span.RestSizeStrength = 1;
|
||||
span.SideDistance = 10000; // Set to a high number so a single < can be used to both compare and test for validity
|
||||
this.Spans.push(span);
|
||||
|
||||
if (control instanceof Window)
|
||||
span.Title = (<Window>control).Title;
|
||||
|
||||
// Add a size constraint for each span
|
||||
let size_constraint = new SizeConstraint();
|
||||
size_constraint.Span = span;
|
||||
size_constraint.Size = span.Max - span.Min;
|
||||
this.SizeConstraints.push(size_constraint);
|
||||
}
|
||||
}
|
||||
|
||||
private ApplySizeConstraints()
|
||||
{
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
let span = constraint.Span;
|
||||
let size = span.Max - span.Min;
|
||||
let center = (span.Min + span.Max) * 0.5;
|
||||
let half_delta_size = (constraint.Size - size) * 0.5;
|
||||
let half_border_size = size * 0.5 + half_delta_size * span.SizeStrength;
|
||||
span.Min = center - half_border_size;
|
||||
span.Max = center + half_border_size;
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyMinimumSizeConstraints()
|
||||
{
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
let span = constraint.Span;
|
||||
|
||||
if (span.Max - span.Min < 20)
|
||||
{
|
||||
let center = (span.Min + span.Max) * 0.5;
|
||||
span.Min = center - 10;
|
||||
span.Max = center + 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildContainerConstraints(container: Container, control_graph: ControlGraph, min_controls: number[], max_controls: number[])
|
||||
{
|
||||
for (let i = 0; i < container.Controls.length; i++)
|
||||
{
|
||||
let min_ref_info = control_graph.RefInfos[i * 4 + this.MinSide];
|
||||
let max_ref_info = control_graph.RefInfos[i * 4 + this.MaxSide];
|
||||
|
||||
// Looking for controls that reference the external container on min/max sides
|
||||
if (min_ref_info.References(container))
|
||||
{
|
||||
let constraint = new ContainerConstraint();
|
||||
constraint.Span = this.Spans[i];
|
||||
constraint.Side = this.MinSide;
|
||||
constraint.Position = 0;
|
||||
this.ContainerConstraints.push(constraint);
|
||||
|
||||
// Track min controls for strength setting
|
||||
min_controls.push(i);
|
||||
}
|
||||
if (max_ref_info.References(container))
|
||||
{
|
||||
let constraint = new ContainerConstraint();
|
||||
constraint.Span = this.Spans[i];
|
||||
constraint.Side = this.MaxSide;
|
||||
constraint.Position = this.ContainerRestSize;
|
||||
this.ContainerConstraints.push(constraint);
|
||||
|
||||
// Track max controls for strength setting
|
||||
max_controls.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyContainerConstraints()
|
||||
{
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
constraint.Span.Min = constraint.Position;
|
||||
else
|
||||
constraint.Span.Max = constraint.Position;
|
||||
}
|
||||
}
|
||||
|
||||
private BuildBufferConstraints(container: Container, control_graph: ControlGraph)
|
||||
{
|
||||
for (let ref of control_graph.Refs)
|
||||
{
|
||||
// Only want sides on the configured axis
|
||||
if (ref.Side != this.MinSide && ref.Side != this.MaxSide)
|
||||
continue;
|
||||
|
||||
// There are two refs for each connection; ensure only one of them is used
|
||||
if (ref.FromIndex < ref.ToIndex)
|
||||
{
|
||||
let constraint = new BufferConstraint();
|
||||
constraint.Span0 = this.Spans[ref.FromIndex];
|
||||
constraint.Side = ref.Side;
|
||||
constraint.Span1 = this.Spans[ref.ToIndex];
|
||||
this.BufferConstraints.push(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyBufferConstraints()
|
||||
{
|
||||
for (let constraint of this.BufferConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
{
|
||||
let span0 = constraint.Span0;
|
||||
let span1 = constraint.Span1;
|
||||
let min = span1.Max;
|
||||
let max = span0.Min;
|
||||
let center = (min + max) * 0.5;
|
||||
let size = max - min;
|
||||
let half_delta_size = (Container.SnapBorderSize - size) * 0.5;
|
||||
let half_new_size = size * 0.5 + half_delta_size * 0.5;
|
||||
span0.Min = center + half_new_size;
|
||||
span1.Max = center - half_new_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
let span0 = constraint.Span0;
|
||||
let span1 = constraint.Span1;
|
||||
let min = span0.Max;
|
||||
let max = span1.Min;
|
||||
let center = (min + max) * 0.5;
|
||||
let size = max - min;
|
||||
let half_delta_size = (Container.SnapBorderSize - size) * 0.5;
|
||||
let half_new_size = size * 0.5 + half_delta_size * 0.5;
|
||||
span1.Min = center + half_new_size;
|
||||
span0.Max = center - half_new_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildSnapConstraints(container: Container, control_graph: ControlGraph)
|
||||
{
|
||||
for (let ref of control_graph.Refs)
|
||||
{
|
||||
if (ref.Side == this.MaxSide && ref.To != container)
|
||||
{
|
||||
let constraint = new SnapConstraint();
|
||||
constraint.MinSpan = this.Spans[ref.FromIndex];
|
||||
constraint.MaxSpan = this.Spans[ref.ToIndex];
|
||||
this.SnapConstraints.push(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IntegerRoundSpans()
|
||||
{
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
span.Min = Math.round(span.Min);
|
||||
span.Max = Math.round(span.Max);
|
||||
}
|
||||
}
|
||||
|
||||
private ApplySnapConstraints()
|
||||
{
|
||||
for (let constraint of this.SnapConstraints)
|
||||
{
|
||||
constraint.MaxSpan.Min = constraint.MinSpan.Max + Container.SnapBorderSize - 1;
|
||||
}
|
||||
|
||||
// TODO: Snap to container
|
||||
}
|
||||
|
||||
private SetInitialSizeStrengths(container: Container, control_graph: ControlGraph, min_controls: number[], max_controls: number[])
|
||||
{
|
||||
let weak_strength = 0.01;
|
||||
let strong_strength = 0.5;
|
||||
|
||||
let side_distance = 0;
|
||||
while (min_controls.length && max_controls.length)
|
||||
{
|
||||
// Mark side distances and set strong strengths before walking further
|
||||
for (let index of min_controls)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SideDistance = side_distance;
|
||||
span.SizeStrength = strong_strength;
|
||||
}
|
||||
for (let index of max_controls)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SideDistance = side_distance;
|
||||
span.SizeStrength = strong_strength;
|
||||
}
|
||||
|
||||
let next_min_controls: number[] = [];
|
||||
let next_max_controls: number[] = [];
|
||||
|
||||
// Make one graph step towards max for the min controls, setting strengths
|
||||
for (let index of min_controls)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
let ref_info = control_graph.RefInfos[index * 4 + this.MaxSide];
|
||||
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = ref_info.GetControlRef(i);
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
|
||||
// If we've hit the container this is a control that is anchored on both sides
|
||||
if (ref.To == container)
|
||||
{
|
||||
// Set it to weak so that it's always collapsable
|
||||
span.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we bump up against a span of equal distance, their anchor point is the graph's middle
|
||||
if (span.SideDistance == span_to.SideDistance)
|
||||
{
|
||||
// Mark both sides as weak to make the equally collapsable
|
||||
span.SizeStrength = weak_strength;
|
||||
span_to.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the other side has a smaller distance then this is a center control
|
||||
if (span.SideDistance > span_to.SideDistance)
|
||||
{
|
||||
// Only the control should be marked for collapse
|
||||
span.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Walk toward the max
|
||||
if (next_min_controls.indexOf(ref.ToIndex) == -1)
|
||||
next_min_controls.push(ref.ToIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Make one graph step towards min for the max controls, not setting strengths
|
||||
for (let index of max_controls)
|
||||
{
|
||||
let ref_info = control_graph.RefInfos[index * 4 + this.MinSide];
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = ref_info.GetControlRef(i);
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
|
||||
// Strengths are already set from the min controls so abort walk when coming up
|
||||
// against a min control
|
||||
if (ref.To == container || span_to.SideDistance != 10000)
|
||||
continue;
|
||||
|
||||
// Walk toward the min
|
||||
if (next_max_controls.indexOf(ref.ToIndex) == -1)
|
||||
next_max_controls.push(ref.ToIndex);
|
||||
}
|
||||
}
|
||||
|
||||
min_controls = next_min_controls;
|
||||
max_controls = next_max_controls;
|
||||
side_distance++;
|
||||
}
|
||||
|
||||
// Record initial size strength for restoration
|
||||
for (let span of this.Spans)
|
||||
span.RestSizeStrength = span.SizeStrength;
|
||||
}
|
||||
|
||||
ReevaluateSizeStrengths(control_graph: ControlGraph)
|
||||
{
|
||||
for (let index = 0; index < this.Spans.length; index++)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SizeStrength = span.RestSizeStrength;
|
||||
|
||||
let min_ref_info = control_graph.RefInfos[index * 4 + this.MinSide];
|
||||
for (let i = 0; i < min_ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = min_ref_info.GetControlRef(i);
|
||||
if (ref.ToIndex != -1)
|
||||
{
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
let size = span_to.Max - span_to.Min;
|
||||
if (size <= 20)
|
||||
{
|
||||
span.SizeStrength = 0.01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let max_ref_info = control_graph.RefInfos[index * 4 + this.MaxSide];
|
||||
for (let i = 0; i < max_ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = max_ref_info.GetControlRef(i);
|
||||
if (ref.ToIndex != -1)
|
||||
{
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
let size = span_to.Max - span_to.Min;
|
||||
if (size <= 20)
|
||||
{
|
||||
span.SizeStrength = 0.01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugLog()
|
||||
{
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
if (span)
|
||||
console.log("Span: ", span.Title, span.Min, "->", span.Max, "...", span.SideDistance, "/", span.SizeStrength);
|
||||
else
|
||||
console.log("Null Span");
|
||||
}
|
||||
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
console.log("Size Constraint: ", constraint.Span.Title, "@", constraint.Size);
|
||||
}
|
||||
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
console.log("Container Constraint: ", constraint.Span.Title, Side[constraint.Side], "@", constraint.Position);
|
||||
}
|
||||
|
||||
for (let constraint of this.BufferConstraints)
|
||||
{
|
||||
console.log("Buffer Constraint: ", constraint.Span0.Title, "->", constraint.Span1.Title, "on", Side[constraint.Side]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
export const enum SnapCode
|
||||
{
|
||||
None = 0,
|
||||
X = 1,
|
||||
Y = 2,
|
||||
}
|
||||
|
||||
function SnapControl(pos: int2, snap_pos: int2, mask: int2, p_mask: int2, n_mask: int2, top_left: int2, bottom_right: int2) : int2
|
||||
{
|
||||
let b = Container.SnapBorderSize;
|
||||
let out_mask = new int2(0);
|
||||
|
||||
// Distance from input position to opposing corners of the control
|
||||
let d_tl = int2.Abs(int2.Sub(pos, top_left));
|
||||
let d_br = int2.Abs(int2.Sub(pos, bottom_right));
|
||||
|
||||
// If any distances are within the snap border, move the snap position to them
|
||||
if (mask.x != 0)
|
||||
{
|
||||
if (d_tl.x < b)
|
||||
{
|
||||
snap_pos.x = top_left.x - p_mask.x;
|
||||
out_mask.x = -1;
|
||||
}
|
||||
if (d_br.x < b)
|
||||
{
|
||||
snap_pos.x = bottom_right.x + n_mask.x;
|
||||
out_mask.x = 1;
|
||||
}
|
||||
}
|
||||
if (mask.y != 0)
|
||||
{
|
||||
if (d_tl.y < b)
|
||||
{
|
||||
snap_pos.y = top_left.y - p_mask.y;
|
||||
out_mask.y = -1;
|
||||
}
|
||||
if (d_br.y < b)
|
||||
{
|
||||
snap_pos.y = bottom_right.y + n_mask.y;
|
||||
out_mask.y = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return out_mask;
|
||||
}
|
||||
|
||||
export function FindSnapControls(container: Container, pos: int2, mask: int2, excluding: Control[], out_controls: [Control, int2][] = null) : [SnapCode, int2]
|
||||
{
|
||||
// Selects between control edge and a border-distance outside the control edge
|
||||
let b = Container.SnapBorderSize;
|
||||
let p_mask = int2.Mul(int2.Max0(mask), new int2(b - 1));
|
||||
let n_mask = int2.Mul(int2.Min0(mask), new int2(-b + 1));
|
||||
|
||||
// Start off with no snap adjustment
|
||||
let snap_pos = pos.Copy();
|
||||
|
||||
// Snap to sibling container bounds
|
||||
let snap_code = SnapCode.None;
|
||||
for (let control of container.Controls)
|
||||
{
|
||||
if (!(control instanceof Container))
|
||||
continue;
|
||||
if (excluding.indexOf(control) != -1)
|
||||
continue;
|
||||
|
||||
var top_left = control.TopLeft;
|
||||
var bottom_right = control.BottomRight;
|
||||
|
||||
let out_mask = SnapControl(
|
||||
pos,
|
||||
snap_pos,
|
||||
mask,
|
||||
p_mask,
|
||||
n_mask,
|
||||
control.TopLeft,
|
||||
control.BottomRight);
|
||||
|
||||
snap_code |= out_mask.x != 0 ? SnapCode.X : 0;
|
||||
snap_code |= out_mask.y != 0 ? SnapCode.Y : 0;
|
||||
|
||||
// Collect sibling controls if asked to
|
||||
if (out_controls && (out_mask.x != 0 || out_mask.y != 0))
|
||||
out_controls.push([control, out_mask]);
|
||||
}
|
||||
|
||||
// Snap to parent container bounds
|
||||
let parent_size = container.ControlParentNode.Size;
|
||||
let out_mask = SnapControl(
|
||||
pos,
|
||||
snap_pos,
|
||||
mask,
|
||||
p_mask,
|
||||
n_mask,
|
||||
new int2(b),
|
||||
int2.Sub(parent_size, new int2(b)));
|
||||
|
||||
snap_code |= out_mask.x != 0 ? SnapCode.X : 0;
|
||||
snap_code |= out_mask.y != 0 ? SnapCode.Y : 0;
|
||||
|
||||
return [ snap_code, snap_pos ];
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
|
||||
interface Window
|
||||
{
|
||||
WMRootNode: DOM.Node;
|
||||
WMRootPanel: WM.Panel;
|
||||
}
|
||||
|
||||
namespace WM
|
||||
{
|
||||
export class Panel
|
||||
{
|
||||
static SnapBorderSize = 5;
|
||||
|
||||
private static TemplateHTML = "<div class='Panel'>/div>";
|
||||
|
||||
// Main generated HTML node for the control
|
||||
// TODO: Can this be made private?
|
||||
Node: DOM.Node;
|
||||
|
||||
// PART: Panel container
|
||||
Container: PanelContainer;
|
||||
|
||||
// Parent panel that is always set, even when the panel node is hidden and not part of the DOM tree
|
||||
private _ParentPanel: Panel;
|
||||
|
||||
// Rectangle coverage
|
||||
// Always ensure position/size are defined as they are required to calculate bottom-right
|
||||
private _Position = new int2(0);
|
||||
private _Size = new int2(0);
|
||||
private _BottomRight = new int2(0);
|
||||
|
||||
// Records current visibility state
|
||||
private _Visible = false;
|
||||
|
||||
constructor(position: int2, size: int2, node?: DOM.Node)
|
||||
{
|
||||
// First-time initialisation of any panel resources
|
||||
if (window.WMRootNode == null)
|
||||
{
|
||||
// TODO: Can fully flesh out root panel size and event handlers here
|
||||
window.WMRootNode = $(document.body);
|
||||
window.WMRootPanel = new Panel(new int2(0), new int2(0), window.WMRootNode);
|
||||
}
|
||||
|
||||
this.Node = node ? node : new DOM.Node(Panel.TemplateHTML);
|
||||
|
||||
// Store position/size directly so that bottom/right can be calculated
|
||||
this._Position = position;
|
||||
this._Size = size;
|
||||
this._BottomRight = int2.Add(this._Position, this._Size);
|
||||
|
||||
// Apply initial settings to the node
|
||||
this.Node.Position = this._Position;
|
||||
this.Node.Size = this._Size;
|
||||
|
||||
// Parent everything to the root to start with
|
||||
window.WMRootPanel.Container.Add(this);
|
||||
|
||||
this.Node.MouseDownEvent.Subscribe(this.OnMouseDown);
|
||||
}
|
||||
|
||||
// Cached node position
|
||||
set Position(position: int2)
|
||||
{
|
||||
this._Position = position;
|
||||
this._BottomRight = int2.Add(this._Position, this._Size);
|
||||
this.Node.Position = position;
|
||||
}
|
||||
get Position() : int2
|
||||
{
|
||||
return this._Position;
|
||||
}
|
||||
|
||||
// Cached node size
|
||||
set Size(size: int2)
|
||||
{
|
||||
this._Size = size;
|
||||
this._BottomRight = int2.Add(this._Position, this._Size);
|
||||
this.Node.Size = size;
|
||||
}
|
||||
get Size() : int2
|
||||
{
|
||||
return this._Size;
|
||||
}
|
||||
|
||||
// Alternative rectangle coverage
|
||||
set TopLeft(tl: int2)
|
||||
{
|
||||
this._Position = tl;
|
||||
this._Size = int2.Sub(this._BottomRight, tl);
|
||||
this.Node.Position = tl;
|
||||
this.Node.Size = this._Size;
|
||||
}
|
||||
get TopLeft() : int2
|
||||
{
|
||||
return this.Position;
|
||||
}
|
||||
set BottomRight(br: int2)
|
||||
{
|
||||
this.Size = int2.Sub(br, this._Position);
|
||||
}
|
||||
get BottomRight() : int2
|
||||
{
|
||||
return this._BottomRight;
|
||||
}
|
||||
|
||||
// Tells whether the panel thinks it's visible or not
|
||||
get Visible() : boolean
|
||||
{
|
||||
return this._Visible;
|
||||
}
|
||||
|
||||
// Panel this one is contained by
|
||||
set ParentPanel(parent_panel : Panel)
|
||||
{
|
||||
this._ParentPanel = parent_panel;
|
||||
}
|
||||
get ParentPanel() : Panel
|
||||
{
|
||||
return this._ParentPanel;
|
||||
}
|
||||
|
||||
// Returns the node which all added panels are parented to
|
||||
get PanelContainerNode() : DOM.Node
|
||||
{
|
||||
return this.Node;
|
||||
}
|
||||
|
||||
// Make the panel visible
|
||||
Show()
|
||||
{
|
||||
// Node will be parented to a panel but might not yet be part of the element tree
|
||||
if (this.Node.ParentElement == null)
|
||||
this.ParentPanel.Node.Append(this.Node);
|
||||
|
||||
this._Visible = true;
|
||||
}
|
||||
|
||||
// Hide the panel
|
||||
Hide()
|
||||
{
|
||||
// Safe to detach a node with no parent; saves a branch here
|
||||
this.Node.Detach();
|
||||
this._Visible = false;
|
||||
}
|
||||
|
||||
SendToTop()
|
||||
{
|
||||
let parent_panel_container = this.ParentPanel.Container;
|
||||
let parent_panels = parent_panel_container.Panels;
|
||||
|
||||
// Push to the back of the parent's panel list
|
||||
let index = parent_panels.indexOf(this);
|
||||
if (index != -1)
|
||||
{
|
||||
parent_panels.splice(index, 1);
|
||||
parent_panels.push(this);
|
||||
|
||||
// Recalculate z-indices for visible sort
|
||||
parent_panel_container.UpdateZIndices();
|
||||
}
|
||||
}
|
||||
|
||||
SendToBottom()
|
||||
{
|
||||
let parent_panel_container = this.ParentPanel.Container;
|
||||
let parent_panels = parent_panel_container.Panels;
|
||||
|
||||
// Push to the front of the parent's panel list
|
||||
let index = parent_panels.indexOf(this);
|
||||
if (index != -1)
|
||||
{
|
||||
parent_panels.splice(index, 1);
|
||||
parent_panels.unshift(this);
|
||||
|
||||
// Recalculate z-indices for visible sort
|
||||
parent_panel_container.UpdateZIndices();
|
||||
}
|
||||
}
|
||||
|
||||
private OnMouseDown = (event: MouseEvent) =>
|
||||
{
|
||||
// Allow bubble-up for this event so that it filters through nested windows
|
||||
this.SendToTop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
namespace WM
|
||||
{
|
||||
export class PanelContainer
|
||||
{
|
||||
// Panel that owns this part
|
||||
Owner: Panel;
|
||||
|
||||
// List of contained panels in z-order
|
||||
Panels: Panel[] = [];
|
||||
|
||||
constructor(owner: Panel)
|
||||
{
|
||||
this.Owner = owner;
|
||||
}
|
||||
|
||||
// Add as a child and show
|
||||
Add(panel: Panel) : Panel
|
||||
{
|
||||
// Remove from any existing parent
|
||||
// Panels always have a parent and so it can be assumed the parent has a panel container
|
||||
panel.ParentPanel.Container.Remove(panel);
|
||||
|
||||
// Parent this panel
|
||||
this.Panels.push(panel);
|
||||
panel.ParentPanel = this.Owner;
|
||||
|
||||
panel.Show();
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
// Hide and remove from this panel
|
||||
Remove(panel: Panel)
|
||||
{
|
||||
panel.Hide();
|
||||
|
||||
// Remove from the panel list and orphan
|
||||
let index = this.Panels.indexOf(panel);
|
||||
this.Panels.splice(index);
|
||||
panel.ParentPanel = window.WMRootPanel;
|
||||
}
|
||||
|
||||
UpdateZIndices()
|
||||
{
|
||||
// TODO: ZINDEX needs to be relative to parent!
|
||||
|
||||
// Set a CSS z-index for each visible panel from the bottom up
|
||||
for (let i = 0; i < this.Panels.length; i++)
|
||||
{
|
||||
let panel = this.Panels[i];
|
||||
if (!panel.Visible)
|
||||
continue;
|
||||
|
||||
// Ensure there's space between each window for the elements inside to be sorted
|
||||
// TODO: Update with full knowledge of child panels
|
||||
let z = (i + 1) * 10;
|
||||
panel.Node.ZIndex = z;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,280 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
export class PanelRef
|
||||
{
|
||||
// Primary sort key, to be combined with Side
|
||||
FromIndex: number;
|
||||
|
||||
// Cached panel reference to save needing to lookup in the parent
|
||||
From: Panel;
|
||||
|
||||
// Which side of the panel the reference is on
|
||||
Side: Side;
|
||||
|
||||
// Panel this references
|
||||
ToIndex: number;
|
||||
To: Panel;
|
||||
|
||||
|
||||
constructor(from_index: number, from: Panel, side: Side, to_index: number, to: Panel)
|
||||
{
|
||||
this.FromIndex = from_index;
|
||||
this.From = from;
|
||||
this.Side = side;
|
||||
this.ToIndex = to_index;
|
||||
this.To = to;
|
||||
}
|
||||
|
||||
get SortIndex() : number
|
||||
{
|
||||
return this.FromIndex * 4 + this.Side;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class PanelRefInfo
|
||||
{
|
||||
// Storing this allows the class to query graph properties without the extra parameter
|
||||
ParentGraph: PanelGraph;
|
||||
|
||||
// Cached panel reference to save needing to lookup in the parent
|
||||
Panel: Panel;
|
||||
|
||||
// Side specified for debug
|
||||
Side: Side;
|
||||
|
||||
// Links in the panel ref array
|
||||
StartRef: number;
|
||||
NbRefs: number;
|
||||
|
||||
constructor(parent_graph: PanelGraph, panel: Panel, side: Side)
|
||||
{
|
||||
this.ParentGraph = parent_graph;
|
||||
this.Panel = panel;
|
||||
this.Side = side;
|
||||
this.StartRef = -1;
|
||||
this.NbRefs = 0;
|
||||
}
|
||||
|
||||
References(panel: Panel) : boolean
|
||||
{
|
||||
for (let i = 0; i < this.NbRefs; i++)
|
||||
{
|
||||
if (this.ParentGraph.Refs[this.StartRef + i].To == panel)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
GetPanelRef(index: number) : PanelRef
|
||||
{
|
||||
if (index < this.NbRefs)
|
||||
return this.ParentGraph.Refs[this.StartRef + index];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
GetSide(side: Side) : PanelRefInfo
|
||||
{
|
||||
if (this.NbRefs == 0)
|
||||
return null;
|
||||
|
||||
let ref = this.ParentGraph.Refs[this.StartRef];
|
||||
return this.ParentGraph.RefInfos[ref.FromIndex * 4 + side];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class PanelGraph
|
||||
{
|
||||
Refs: PanelRef[] = [];
|
||||
|
||||
RefInfos: PanelRefInfo[] = [];
|
||||
|
||||
Build(container: PanelContainer)
|
||||
{
|
||||
// Clear existing references
|
||||
this.Refs = [ ];
|
||||
this.RefInfos = [ ];
|
||||
|
||||
// Mark all panels as unvisited
|
||||
let panel_visited: boolean[] = [];
|
||||
for (let i = 0; i < container.Panels.length; i++)
|
||||
panel_visited.push(false);
|
||||
|
||||
// Build references for each panel
|
||||
for (let i = 0; i < container.Panels.length; i++)
|
||||
{
|
||||
if (panel_visited[i])
|
||||
continue;
|
||||
|
||||
let child_panel = container.Panels[i];
|
||||
if (!child_panel.Visible)
|
||||
continue;
|
||||
|
||||
// Exempt rulers from the graph
|
||||
// TODO: Add some type traits or perhaps a property that can control this instead of just limiting to rulers
|
||||
if (child_panel instanceof Ruler)
|
||||
continue;
|
||||
|
||||
this.BuildRefs(child_panel, container, panel_visited);
|
||||
}
|
||||
|
||||
// Sort panel references, packing panels/sides next to each other
|
||||
this.Refs.sort((a: PanelRef, b: PanelRef) : number =>
|
||||
{
|
||||
return a.SortIndex - b.SortIndex;
|
||||
});
|
||||
|
||||
// Initialise the panel ref info array
|
||||
for (let i = 0; i < container.Panels.length * 4; i++)
|
||||
{
|
||||
let child_panel = container.Panels[i >> 2];
|
||||
this.RefInfos.push(new PanelRefInfo(this, child_panel, i & 3));
|
||||
}
|
||||
|
||||
// Tell each panel where its reference list starts and ends
|
||||
let last_sort_index = -1;
|
||||
for (let i = 0; i < this.Refs.length; i++)
|
||||
{
|
||||
let ref = this.Refs[i];
|
||||
let sort_index = ref.SortIndex;
|
||||
let ref_info = this.RefInfos[sort_index];
|
||||
|
||||
if (last_sort_index != sort_index)
|
||||
{
|
||||
ref_info.StartRef = i;
|
||||
last_sort_index = sort_index;
|
||||
}
|
||||
|
||||
ref_info.NbRefs++;
|
||||
}
|
||||
}
|
||||
|
||||
private BuildRefs(root_panel: Panel, container: PanelContainer, panel_visited: boolean[])
|
||||
{
|
||||
// First panel to visit is the root panel
|
||||
let to_visit_panels: Panel[] = [ root_panel ];
|
||||
|
||||
// Loop through any panels left to visit
|
||||
for (let panel_0 of to_visit_panels)
|
||||
{
|
||||
// It's possible for the same panel to be pushed onto the to-visit list more than once
|
||||
let panel_0_index = container.Panels.indexOf(panel_0);
|
||||
if (panel_visited[panel_0_index])
|
||||
continue;
|
||||
panel_visited[panel_0_index] = true;
|
||||
|
||||
let tl_0 = panel_0.TopLeft;
|
||||
let br_0 = panel_0.BottomRight;
|
||||
|
||||
// Add references to the parent panel
|
||||
let parent_panel = container.Owner;
|
||||
let b = Panel.SnapBorderSize;
|
||||
let s = parent_panel.PanelContainerNode.Size;
|
||||
if (tl_0.x <= b)
|
||||
this.Refs.push(new PanelRef(panel_0_index, panel_0, Side.Left, -1, parent_panel));
|
||||
if (tl_0.y <= b)
|
||||
this.Refs.push(new PanelRef(panel_0_index, panel_0, Side.Top, -1, parent_panel));
|
||||
if (br_0.x >= s.x - b)
|
||||
this.Refs.push(new PanelRef(panel_0_index, panel_0, Side.Right, -1, parent_panel));
|
||||
if (br_0.y >= s.y - b)
|
||||
this.Refs.push(new PanelRef(panel_0_index, panel_0, Side.Bottom, -1, parent_panel));
|
||||
|
||||
// Check candidate panels for auto-anchor intersection
|
||||
for (let panel_1 of container.Panels)
|
||||
{
|
||||
// If a panel has been previous visited, no need to add forward links as back
|
||||
// links would have been added when it was visited
|
||||
let panel_1_index = container.Panels.indexOf(panel_1);
|
||||
if (panel_visited[panel_1_index])
|
||||
continue;
|
||||
|
||||
if (!panel_1.Visible)
|
||||
continue;
|
||||
|
||||
// Exempt rulers from the graph
|
||||
// TODO: Add some type traits or perhaps a property that can control this instead of just limiting to rulers
|
||||
if (panel_1 instanceof Ruler)
|
||||
continue;
|
||||
|
||||
let tl_1 = panel_1.TopLeft;
|
||||
let br_1 = panel_1.BottomRight;
|
||||
|
||||
let side_0 = Side.None;
|
||||
let side_1 = Side.None;
|
||||
|
||||
// Check for vertical separating axis
|
||||
if (tl_1.y - br_0.y < 0 && tl_0.y - br_1.y < 0)
|
||||
{
|
||||
// Check left/right edge intersection
|
||||
if (Math.abs(tl_0.x - br_1.x) < b)
|
||||
{
|
||||
side_0 = Side.Left;
|
||||
side_1 = Side.Right;
|
||||
}
|
||||
if (Math.abs(br_0.x - tl_1.x) < b)
|
||||
{
|
||||
side_0 = Side.Right;
|
||||
side_1 = Side.Left;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for horizontal separating axis
|
||||
if (tl_1.x - br_0.x < 0 && tl_0.x - br_1.x < 0)
|
||||
{
|
||||
// Check top/bottom edge intersection
|
||||
if (Math.abs(tl_0.y - br_1.y) < b)
|
||||
{
|
||||
side_0 = Side.Top;
|
||||
side_1 = Side.Bottom;
|
||||
}
|
||||
if (Math.abs(br_0.y - tl_1.y) < b)
|
||||
{
|
||||
side_0 = Side.Bottom;
|
||||
side_1 = Side.Top;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate references for any intersection
|
||||
if (side_0 != Side.None)
|
||||
{
|
||||
this.Refs.push(new PanelRef(panel_0_index, panel_0, side_0, panel_1_index, panel_1));
|
||||
this.Refs.push(new PanelRef(panel_1_index, panel_1, side_1, panel_0_index, panel_0));
|
||||
to_visit_panels.push(panel_1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugLog()
|
||||
{
|
||||
console.log("\n--- DebugLog --------------------------------");
|
||||
|
||||
let x = Side[Side.Top];
|
||||
for (let ref_info of this.RefInfos)
|
||||
{
|
||||
if (!(ref_info.Panel instanceof Panel))
|
||||
continue;
|
||||
if (ref_info.NbRefs == 0)
|
||||
continue;
|
||||
|
||||
let names = "";
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
//let window = this.Refs[ref_info.StartRef + i].To as Window;
|
||||
//names += window.Title + ", ";
|
||||
}
|
||||
|
||||
//console.log((<Window>ref_info.Control).Title, Side[ref_info.Side] + ": ", names);
|
||||
}
|
||||
|
||||
/*for (let ref of this.Refs)
|
||||
{
|
||||
console.log((<Window>ref.From).Title, ref.Side, (<Window>ref.To).Title);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,516 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
class Span
|
||||
{
|
||||
// Only for DebugLog
|
||||
Title: string;
|
||||
|
||||
// Source panel for copying simulation back
|
||||
Panel: Panel;
|
||||
|
||||
Min: number;
|
||||
Max: number;
|
||||
|
||||
RestSizeStrength: number;
|
||||
SizeStrength: number;
|
||||
|
||||
// Number of panels between this one and the side of the container
|
||||
SideDistance: number;
|
||||
}
|
||||
|
||||
class SizeConstraint
|
||||
{
|
||||
Span: Span;
|
||||
Size: number;
|
||||
}
|
||||
|
||||
class ContainerConstraint
|
||||
{
|
||||
Span: Span;
|
||||
Side: Side;
|
||||
Position: number;
|
||||
}
|
||||
|
||||
class BufferConstraint
|
||||
{
|
||||
Span0: Span;
|
||||
Span1: Span;
|
||||
Side: Side;
|
||||
}
|
||||
|
||||
class SnapConstraint
|
||||
{
|
||||
MinSpan: Span;
|
||||
MaxSpan: Span;
|
||||
}
|
||||
|
||||
// TODO: Need to unify snapped panels into one constraint instead of multiple
|
||||
export class PanelSizer
|
||||
{
|
||||
// Allow the sizer to work independently on horizontal/vertical axes
|
||||
MinSide: Side;
|
||||
MaxSide: Side;
|
||||
|
||||
ContainerRestSize: number;
|
||||
ContainerSize: number;
|
||||
|
||||
Spans: Span[] = [];
|
||||
|
||||
ContainerConstraints: ContainerConstraint[] = [];
|
||||
BufferConstraints: BufferConstraint[] = [];
|
||||
SizeConstraints: SizeConstraint[] = [];
|
||||
SnapConstraints: SnapConstraint[] = [];
|
||||
|
||||
Clear()
|
||||
{
|
||||
this.Spans = [];
|
||||
this.ContainerConstraints = [];
|
||||
this.BufferConstraints = [];
|
||||
this.SizeConstraints = [];
|
||||
this.SnapConstraints = [];
|
||||
}
|
||||
|
||||
Build(base_side: Side, container: PanelContainer, panel_graph: PanelGraph)
|
||||
{
|
||||
this.MinSide = base_side;
|
||||
this.MaxSide = base_side + 1;
|
||||
|
||||
if (base_side == Side.Left)
|
||||
this.ContainerRestSize = container.Owner.PanelContainerNode.Size.x;
|
||||
else
|
||||
this.ContainerRestSize = container.Owner.PanelContainerNode.Size.y;
|
||||
|
||||
// Clear previous constraints
|
||||
this.Clear();
|
||||
|
||||
// Build the span list
|
||||
this.BuildSpans(container);
|
||||
|
||||
// Build constraints
|
||||
let min_panels: number[] = [];
|
||||
let max_panels: number[] = [];
|
||||
this.BuildContainerConstraints(container, panel_graph, min_panels, max_panels);
|
||||
this.BuildBufferConstraints(panel_graph);
|
||||
this.BuildSnapConstraints(container.Owner, panel_graph);
|
||||
|
||||
this.SetInitialSizeStrengths(container.Owner, panel_graph, min_panels, max_panels);
|
||||
}
|
||||
|
||||
ChangeSize(new_size: number, panel_graph: PanelGraph)
|
||||
{
|
||||
// Update container constraints with new size
|
||||
this.ContainerSize = new_size;
|
||||
let half_delta_size = (this.ContainerRestSize - new_size) / 2;
|
||||
let min_offset = half_delta_size + Panel.SnapBorderSize;
|
||||
let max_offset = this.ContainerRestSize - min_offset;
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
constraint.Position = min_offset;
|
||||
else
|
||||
constraint.Position = max_offset;
|
||||
}
|
||||
|
||||
// Relax
|
||||
for (let i = 0; i < 50; i++)
|
||||
{
|
||||
this.ApplySizeConstraints();
|
||||
this.ApplyMinimumSizeConstraints();
|
||||
this.ApplyBufferConstraints();
|
||||
|
||||
// Do this here before non-spring constraints
|
||||
this.IntegerRoundSpans();
|
||||
|
||||
this.ApplyContainerConstraints();
|
||||
|
||||
// HERE
|
||||
this.ReevaluateSizeStrengths(panel_graph);
|
||||
}
|
||||
|
||||
// TODO: Finish with a snap! Can that be made into a constraint?
|
||||
// Problem is that multiple controls may be out of line
|
||||
this.ApplySnapConstraints();
|
||||
this.ApplyContainerConstraints();
|
||||
|
||||
// Copy simulation back to the controls
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
if (this.MinSide == Side.Left)
|
||||
{
|
||||
span.Panel.Position = new int2(span.Min - half_delta_size, span.Panel.Position.y);
|
||||
span.Panel.Size = new int2(span.Max - span.Min, span.Panel.Size.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
span.Panel.Position = new int2(span.Panel.Position.x, span.Min - half_delta_size);
|
||||
span.Panel.Size = new int2(span.Panel.Size.x, span.Max - span.Min);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildSpans(container: PanelContainer)
|
||||
{
|
||||
for (let child_panel of container.Panels)
|
||||
{
|
||||
// Anything that's exempt from sizing (e.g. a Ruler) still needs an entry in the array, even if it's empty
|
||||
if (child_panel instanceof Ruler)
|
||||
{
|
||||
this.Spans.push(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set initial parameters
|
||||
let span = new Span();
|
||||
span.Panel = child_panel;
|
||||
if (this.MinSide == Side.Left)
|
||||
{
|
||||
span.Min = child_panel.TopLeft.x;
|
||||
span.Max = child_panel.BottomRight.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
span.Min = child_panel.TopLeft.y;
|
||||
span.Max = child_panel.BottomRight.y;
|
||||
}
|
||||
span.SizeStrength = 1;
|
||||
span.RestSizeStrength = 1;
|
||||
span.SideDistance = 10000; // Set to a high number so a single < can be used to both compare and test for validity
|
||||
this.Spans.push(span);
|
||||
|
||||
//if (control instanceof Window)
|
||||
// span.Title = (<Window>control).Title;
|
||||
span.Title = "NEEDS TO BE SET TO WINDOW";
|
||||
|
||||
// Add a size constraint for each span
|
||||
let size_constraint = new SizeConstraint();
|
||||
size_constraint.Span = span;
|
||||
size_constraint.Size = span.Max - span.Min;
|
||||
this.SizeConstraints.push(size_constraint);
|
||||
}
|
||||
}
|
||||
|
||||
private ApplySizeConstraints()
|
||||
{
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
let span = constraint.Span;
|
||||
let size = span.Max - span.Min;
|
||||
let center = (span.Min + span.Max) * 0.5;
|
||||
let half_delta_size = (constraint.Size - size) * 0.5;
|
||||
let half_border_size = size * 0.5 + half_delta_size * span.SizeStrength;
|
||||
span.Min = center - half_border_size;
|
||||
span.Max = center + half_border_size;
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyMinimumSizeConstraints()
|
||||
{
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
let span = constraint.Span;
|
||||
|
||||
if (span.Max - span.Min < 20)
|
||||
{
|
||||
let center = (span.Min + span.Max) * 0.5;
|
||||
span.Min = center - 10;
|
||||
span.Max = center + 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildContainerConstraints(container: PanelContainer, panel_graph: PanelGraph, min_panels: number[], max_panels: number[])
|
||||
{
|
||||
for (let i = 0; i < container.Panels.length; i++)
|
||||
{
|
||||
let min_ref_info = panel_graph.RefInfos[i * 4 + this.MinSide];
|
||||
let max_ref_info = panel_graph.RefInfos[i * 4 + this.MaxSide];
|
||||
|
||||
// Looking for panels that reference the external container on min/max sides
|
||||
if (min_ref_info.References(container.Owner))
|
||||
{
|
||||
let constraint = new ContainerConstraint();
|
||||
constraint.Span = this.Spans[i];
|
||||
constraint.Side = this.MinSide;
|
||||
constraint.Position = 0;
|
||||
this.ContainerConstraints.push(constraint);
|
||||
|
||||
// Track min panels for strength setting
|
||||
min_panels.push(i);
|
||||
}
|
||||
if (max_ref_info.References(container.Owner))
|
||||
{
|
||||
let constraint = new ContainerConstraint();
|
||||
constraint.Span = this.Spans[i];
|
||||
constraint.Side = this.MaxSide;
|
||||
constraint.Position = this.ContainerRestSize;
|
||||
this.ContainerConstraints.push(constraint);
|
||||
|
||||
// Track max panels for strength setting
|
||||
max_panels.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyContainerConstraints()
|
||||
{
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
constraint.Span.Min = constraint.Position;
|
||||
else
|
||||
constraint.Span.Max = constraint.Position;
|
||||
}
|
||||
}
|
||||
|
||||
private BuildBufferConstraints(panel_graph: PanelGraph)
|
||||
{
|
||||
for (let ref of panel_graph.Refs)
|
||||
{
|
||||
// Only want sides on the configured axis
|
||||
if (ref.Side != this.MinSide && ref.Side != this.MaxSide)
|
||||
continue;
|
||||
|
||||
// There are two refs for each connection; ensure only one of them is used
|
||||
if (ref.FromIndex < ref.ToIndex)
|
||||
{
|
||||
let constraint = new BufferConstraint();
|
||||
constraint.Span0 = this.Spans[ref.FromIndex];
|
||||
constraint.Side = ref.Side;
|
||||
constraint.Span1 = this.Spans[ref.ToIndex];
|
||||
this.BufferConstraints.push(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyBufferConstraints()
|
||||
{
|
||||
for (let constraint of this.BufferConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
{
|
||||
let span0 = constraint.Span0;
|
||||
let span1 = constraint.Span1;
|
||||
let min = span1.Max;
|
||||
let max = span0.Min;
|
||||
let center = (min + max) * 0.5;
|
||||
let size = max - min;
|
||||
let half_delta_size = (Container.SnapBorderSize - size) * 0.5;
|
||||
let half_new_size = size * 0.5 + half_delta_size * 0.5;
|
||||
span0.Min = center + half_new_size;
|
||||
span1.Max = center - half_new_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
let span0 = constraint.Span0;
|
||||
let span1 = constraint.Span1;
|
||||
let min = span0.Max;
|
||||
let max = span1.Min;
|
||||
let center = (min + max) * 0.5;
|
||||
let size = max - min;
|
||||
let half_delta_size = (Container.SnapBorderSize - size) * 0.5;
|
||||
let half_new_size = size * 0.5 + half_delta_size * 0.5;
|
||||
span1.Min = center + half_new_size;
|
||||
span0.Max = center - half_new_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildSnapConstraints(panel: Panel, panel_graph: PanelGraph)
|
||||
{
|
||||
for (let ref of panel_graph.Refs)
|
||||
{
|
||||
if (ref.Side == this.MaxSide && ref.To != panel)
|
||||
{
|
||||
let constraint = new SnapConstraint();
|
||||
constraint.MinSpan = this.Spans[ref.FromIndex];
|
||||
constraint.MaxSpan = this.Spans[ref.ToIndex];
|
||||
this.SnapConstraints.push(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IntegerRoundSpans()
|
||||
{
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
span.Min = Math.round(span.Min);
|
||||
span.Max = Math.round(span.Max);
|
||||
}
|
||||
}
|
||||
|
||||
private ApplySnapConstraints()
|
||||
{
|
||||
for (let constraint of this.SnapConstraints)
|
||||
{
|
||||
constraint.MaxSpan.Min = constraint.MinSpan.Max + Container.SnapBorderSize - 1;
|
||||
}
|
||||
|
||||
// TODO: Snap to container
|
||||
}
|
||||
|
||||
private SetInitialSizeStrengths(panel: Panel, panel_graph: PanelGraph, min_panels: number[], max_panels: number[])
|
||||
{
|
||||
let weak_strength = 0.01;
|
||||
let strong_strength = 0.5;
|
||||
|
||||
let side_distance = 0;
|
||||
while (min_panels.length && max_panels.length)
|
||||
{
|
||||
// Mark side distances and set strong strengths before walking further
|
||||
for (let index of min_panels)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SideDistance = side_distance;
|
||||
span.SizeStrength = strong_strength;
|
||||
}
|
||||
for (let index of max_panels)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SideDistance = side_distance;
|
||||
span.SizeStrength = strong_strength;
|
||||
}
|
||||
|
||||
let next_min_panels: number[] = [];
|
||||
let next_max_panels: number[] = [];
|
||||
|
||||
// Make one graph step towards max for the min panels, setting strengths
|
||||
for (let index of min_panels)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
let ref_info = panel_graph.RefInfos[index * 4 + this.MaxSide];
|
||||
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = ref_info.GetPanelRef(i);
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
|
||||
// If we've hit the container this is a panel that is anchored on both sides
|
||||
if (ref.To == panel)
|
||||
{
|
||||
// Set it to weak so that it's always collapsable
|
||||
span.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we bump up against a span of equal distance, their anchor point is the graph's middle
|
||||
if (span.SideDistance == span_to.SideDistance)
|
||||
{
|
||||
// Mark both sides as weak to make them equally collapsable
|
||||
span.SizeStrength = weak_strength;
|
||||
span_to.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the other side has a smaller distance then this is a center control
|
||||
if (span.SideDistance > span_to.SideDistance)
|
||||
{
|
||||
// Only the control should be marked for collapse
|
||||
span.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Walk toward the max
|
||||
if (next_min_panels.indexOf(ref.ToIndex) == -1)
|
||||
next_min_panels.push(ref.ToIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Make one graph step towards min for the max panels, not setting strengths
|
||||
for (let index of max_panels)
|
||||
{
|
||||
let ref_info = panel_graph.RefInfos[index * 4 + this.MinSide];
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = ref_info.GetPanelRef(i);
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
|
||||
// Strengths are already set from the min panels so abort walk when coming up
|
||||
// against a min panel
|
||||
if (ref.To == panel || span_to.SideDistance != 10000)
|
||||
continue;
|
||||
|
||||
// Walk toward the min
|
||||
if (next_max_panels.indexOf(ref.ToIndex) == -1)
|
||||
next_max_panels.push(ref.ToIndex);
|
||||
}
|
||||
}
|
||||
|
||||
min_panels = next_min_panels;
|
||||
max_panels = next_max_panels;
|
||||
side_distance++;
|
||||
}
|
||||
|
||||
// Record initial size strength for restoration
|
||||
for (let span of this.Spans)
|
||||
span.RestSizeStrength = span.SizeStrength;
|
||||
}
|
||||
|
||||
ReevaluateSizeStrengths(panel_graph: PanelGraph)
|
||||
{
|
||||
for (let index = 0; index < this.Spans.length; index++)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SizeStrength = span.RestSizeStrength;
|
||||
|
||||
let min_ref_info = panel_graph.RefInfos[index * 4 + this.MinSide];
|
||||
for (let i = 0; i < min_ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = min_ref_info.GetPanelRef(i);
|
||||
if (ref.ToIndex != -1)
|
||||
{
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
let size = span_to.Max - span_to.Min;
|
||||
if (size <= 20)
|
||||
{
|
||||
span.SizeStrength = 0.01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let max_ref_info = panel_graph.RefInfos[index * 4 + this.MaxSide];
|
||||
for (let i = 0; i < max_ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = max_ref_info.GetPanelRef(i);
|
||||
if (ref.ToIndex != -1)
|
||||
{
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
let size = span_to.Max - span_to.Min;
|
||||
if (size <= 20)
|
||||
{
|
||||
span.SizeStrength = 0.01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugLog()
|
||||
{
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
if (span)
|
||||
console.log("Span: ", span.Title, span.Min, "->", span.Max, "...", span.SideDistance, "/", span.SizeStrength);
|
||||
else
|
||||
console.log("Null Span");
|
||||
}
|
||||
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
console.log("Size Constraint: ", constraint.Span.Title, "@", constraint.Size);
|
||||
}
|
||||
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
console.log("Container Constraint: ", constraint.Span.Title, Side[constraint.Side], "@", constraint.Position);
|
||||
}
|
||||
|
||||
for (let constraint of this.BufferConstraints)
|
||||
{
|
||||
console.log("Buffer Constraint: ", constraint.Span0.Title, "->", constraint.Span1.Title, "on", Side[constraint.Side]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
export const enum RulerOrient
|
||||
{
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
export class Ruler extends Control
|
||||
{
|
||||
static TemplateHTML = "<div class='Ruler'></div>";
|
||||
|
||||
// Big enough to span entire screen while being clipped by parent
|
||||
static Size = 10000;
|
||||
|
||||
// Current orientation
|
||||
private _Orient: RulerOrient;
|
||||
|
||||
private static Position2D(orient: RulerOrient, position: number) : int2
|
||||
{
|
||||
return orient == RulerOrient.Horizontal ?
|
||||
new int2(0, position) :
|
||||
new int2(position, 0);
|
||||
}
|
||||
|
||||
private static Size2D(orient: RulerOrient) : int2
|
||||
{
|
||||
return orient == RulerOrient.Horizontal ?
|
||||
new int2(Ruler.Size, 0) :
|
||||
new int2(0, Ruler.Size);
|
||||
}
|
||||
|
||||
constructor(orient: RulerOrient, position: number)
|
||||
{
|
||||
super(new DOM.Node(Ruler.TemplateHTML),
|
||||
Ruler.Position2D(orient, position),
|
||||
Ruler.Size2D(orient));
|
||||
|
||||
this._Orient = orient;
|
||||
}
|
||||
|
||||
SetPosition(position: number) : void
|
||||
{
|
||||
this.Position = Ruler.Position2D(this._Orient, position);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
|
||||
// TODO: Hidden state, z-index, just overwrites existing data, doesn't save new layouts
|
||||
|
||||
namespace WM
|
||||
{
|
||||
class SavedControl
|
||||
{
|
||||
ID: number;
|
||||
Position: int2;
|
||||
Size: int2;
|
||||
ZIndex: number;
|
||||
}
|
||||
|
||||
class SavedContainer extends SavedControl
|
||||
{
|
||||
Controls: SavedContainer[] = [ ];
|
||||
}
|
||||
|
||||
class SavedWindow extends SavedContainer
|
||||
{
|
||||
Title: string;
|
||||
}
|
||||
|
||||
function BuildSavedContainerList(container: Container, saved_container: SavedContainer)
|
||||
{
|
||||
for (let control of container.Controls)
|
||||
{
|
||||
if (control instanceof Window)
|
||||
saved_container.Controls.push(BuildSavedWindow(control as Window));
|
||||
else if (control instanceof Container)
|
||||
saved_container.Controls.push(BuildSavedContainer(control as Container));
|
||||
}
|
||||
}
|
||||
|
||||
function BuildSavedControl(control: Control, saved_control: SavedControl) : void
|
||||
{
|
||||
saved_control.ID = control.ID;
|
||||
saved_control.Position = control.Position;
|
||||
saved_control.Size = control.Size;
|
||||
saved_control.ZIndex = control.ZIndex;
|
||||
}
|
||||
|
||||
function BuildSavedContainer(container: Container) : SavedContainer
|
||||
{
|
||||
let saved_container = new SavedContainer();
|
||||
BuildSavedControl(container, saved_container);
|
||||
BuildSavedContainerList(container, saved_container);
|
||||
return saved_container;
|
||||
}
|
||||
|
||||
function BuildSavedWindow(window: Window) : SavedWindow
|
||||
{
|
||||
let saved_window = new SavedWindow();
|
||||
BuildSavedControl(window, saved_window);
|
||||
saved_window.Title = window.Title;
|
||||
BuildSavedContainerList(window, saved_window);
|
||||
return saved_window;
|
||||
}
|
||||
|
||||
export function SaveContainer(container: Container) : string
|
||||
{
|
||||
let saved_container = BuildSavedContainer(container);
|
||||
return JSON.stringify(saved_container);
|
||||
}
|
||||
|
||||
function ApplyContainerList(container: Container, saved_container: SavedContainer)
|
||||
{
|
||||
if (saved_container.Controls === undefined)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < saved_container.Controls.length; i++)
|
||||
{
|
||||
let child_saved_control = saved_container.Controls[i];
|
||||
|
||||
// Search for control with matching ID
|
||||
for (let j = 0; j < container.Controls.length; j++)
|
||||
{
|
||||
let child_control = container.Controls[j];
|
||||
if (child_control.ID == child_saved_control.ID)
|
||||
{
|
||||
if (child_control instanceof Window)
|
||||
ApplyWindow(child_control as Window, <SavedWindow>child_saved_control);
|
||||
else if (child_control instanceof Container)
|
||||
ApplyContainer(child_control as Container, <SavedContainer>child_saved_control);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ApplyControl(control: Control, saved_control: SavedControl)
|
||||
{
|
||||
if (saved_control.Position !== undefined)
|
||||
control.Position = new int2(saved_control.Position.x, saved_control.Position.y);
|
||||
|
||||
if (saved_control.Size !== undefined)
|
||||
control.Size = new int2(saved_control.Size.x, saved_control.Size.y);
|
||||
|
||||
if (saved_control.ZIndex !== undefined && saved_control.ZIndex != null)
|
||||
control.ZIndex = saved_control.ZIndex;
|
||||
}
|
||||
|
||||
function ApplyWindow(window: Window, saved_window: SavedWindow)
|
||||
{
|
||||
ApplyControl(window, saved_window);
|
||||
|
||||
if (saved_window.Title !== undefined)
|
||||
window.Title = <string>saved_window.Title;
|
||||
|
||||
ApplyContainerList(window, saved_window);
|
||||
}
|
||||
|
||||
function ApplyContainer(container: Container, saved_container: SavedContainer)
|
||||
{
|
||||
ApplyControl(container, saved_container);
|
||||
ApplyContainerList(container, saved_container);
|
||||
}
|
||||
|
||||
export function LoadContainer(container: Container, input: string)
|
||||
{
|
||||
let saved_container = JSON.parse(input);
|
||||
ApplyContainer(container, saved_container);
|
||||
}
|
||||
}
|
|
@ -1,702 +0,0 @@
|
|||
|
||||
// TODO: If window is made embedded, remove window sizing nodes
|
||||
|
||||
namespace WM
|
||||
{
|
||||
export enum Side
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
None,
|
||||
}
|
||||
|
||||
export class Window extends Container
|
||||
{
|
||||
static TemplateHTML = `
|
||||
<div class='Window'>
|
||||
<div class='WindowTitleBar'>
|
||||
<div class='WindowTitleBarText notextsel' style='float:left'>Window Title Bar</div>
|
||||
<div class='WindowTitleBarClose notextsel' style='float:right'>O</div>
|
||||
</div>
|
||||
<div class='WindowBody'>
|
||||
<div class='WindowBodyDebug'></div>
|
||||
</div>
|
||||
<div class='WindowSizeLeft'></div>
|
||||
<div class='WindowSizeRight'></div>
|
||||
<div class='WindowSizeTop'></div>
|
||||
<div class='WindowSizeBottom'></div>
|
||||
</div>`
|
||||
|
||||
// Internal nodes
|
||||
private TitleBarNode: DOM.Node;
|
||||
private TitleBarTextNode: DOM.Node;
|
||||
private TitleBarCloseNode: DOM.Node;
|
||||
private BodyNode: DOM.Node;
|
||||
private DebugNode: DOM.Node;
|
||||
private SizeLeftNode: DOM.Node;
|
||||
private SizeRightNode: DOM.Node;
|
||||
private SizeTopNode: DOM.Node;
|
||||
private SizeBottomNode: DOM.Node;
|
||||
|
||||
// Size as specified in CSS
|
||||
private SideBarSize: number;
|
||||
|
||||
// Transient parameters for mouse move events
|
||||
private DragMouseStartPosition: int2;
|
||||
private DragWindowStartPosition: int2;
|
||||
private DragWindowStartSize: int2;
|
||||
private MouseOffset: int2;
|
||||
|
||||
private ActiveTouchID: number;
|
||||
|
||||
// List of controls that are auto-anchored to a container edge during sizing
|
||||
private AnchorControls: [Control, int2][];
|
||||
|
||||
// Transient snap rulers for each side
|
||||
private SnapRulers: Ruler[] = [ null, null, null, null ];
|
||||
|
||||
// Used to track whether a sizer is being held as opposed to moved
|
||||
private SizerMoved: boolean = false;
|
||||
|
||||
// Transient delegates for mouse size events
|
||||
private OnSizeDelegate: EventListener;
|
||||
private OnEndSizeDelegate: EventListener;
|
||||
|
||||
constructor(title: string, position: int2, size: int2)
|
||||
{
|
||||
// Create root node
|
||||
super(position, size, new DOM.Node(Window.TemplateHTML));
|
||||
|
||||
// Locate internal nodes
|
||||
this.TitleBarNode = this.Node.Find(".WindowTitleBar");
|
||||
this.TitleBarTextNode = this.Node.Find(".WindowTitleBarText");
|
||||
this.TitleBarCloseNode = this.Node.Find(".WindowTitleBarClose");
|
||||
this.BodyNode = this.Node.Find(".WindowBody");
|
||||
this.DebugNode = this.Node.Find(".WindowBodyDebug");
|
||||
this.SizeLeftNode = this.Node.Find(".WindowSizeLeft");
|
||||
this.SizeRightNode = this.Node.Find(".WindowSizeRight");
|
||||
this.SizeTopNode = this.Node.Find(".WindowSizeTop");
|
||||
this.SizeBottomNode = this.Node.Find(".WindowSizeBottom");
|
||||
|
||||
// Query CSS properties
|
||||
let body_styles = window.getComputedStyle(document.body);
|
||||
let side_bar_size = body_styles.getPropertyValue('--SideBarSize');
|
||||
this.SideBarSize = parseInt(side_bar_size);
|
||||
|
||||
// Apply the title bar text
|
||||
this.Title = title;
|
||||
|
||||
// Window move handler
|
||||
this.TitleBarNode.MouseDownEvent.Subscribe(this.OnMouseStart);
|
||||
this.TitleBarNode.TouchStartEvent.Subscribe(this.OnTouchStart);
|
||||
|
||||
// Cursor change handlers as the mouse moves over sizers
|
||||
this.SizeLeftNode.MouseMoveEvent.Subscribe(this.OnMoveOverSize);
|
||||
this.SizeRightNode.MouseMoveEvent.Subscribe(this.OnMoveOverSize);
|
||||
this.SizeTopNode.MouseMoveEvent.Subscribe(this.OnMoveOverSize);
|
||||
this.SizeBottomNode.MouseMoveEvent.Subscribe(this.OnMoveOverSize);
|
||||
|
||||
// Window sizing handlers
|
||||
this.SizeLeftNode.MouseDownEvent.Subscribe((event: MouseEvent) => { this.OnBeginSize(event, null, true); });
|
||||
this.SizeRightNode.MouseDownEvent.Subscribe((event: MouseEvent) => { this.OnBeginSize(event, null, true); });
|
||||
this.SizeTopNode.MouseDownEvent.Subscribe((event: MouseEvent) => { this.OnBeginSize(event, null, true); });
|
||||
this.SizeBottomNode.MouseDownEvent.Subscribe((event: MouseEvent) => { this.OnBeginSize(event, null, true); });
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
|
||||
|
||||
// ----- WM.Control Overrides --------------------------------------------------------
|
||||
|
||||
|
||||
/*Show() : void
|
||||
{
|
||||
super.Show();
|
||||
|
||||
// Build control graph for all controls in this container
|
||||
this.ControlGraph.Build(this);
|
||||
|
||||
// Auto-anchor to nearby controls on each show
|
||||
// This catches initial adding of controls to a new window and
|
||||
// any size changes while the window is invisible
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container)
|
||||
{
|
||||
console.log("SHOW ", this.Title);
|
||||
|
||||
let snap_tl = parent_container.GetSnapControls(this.TopLeft, new int2(-1, -1), [ this ], null, 0);
|
||||
if (snap_tl[0] != SnapCode.None)
|
||||
{
|
||||
console.log("Snapped!");
|
||||
this.Position = snap_tl[1];
|
||||
}
|
||||
|
||||
let snap_br = parent_container.GetSnapControls(this.BottomRight, new int2(1, 1), [ this ], null, 0);
|
||||
if (snap_br[0] != SnapCode.None)
|
||||
{
|
||||
console.log("Snapped!");
|
||||
this.Position = int2.Sub(snap_br[1], this.Size);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
// Uncached window title text so that any old HTML can be used
|
||||
get Title() : string
|
||||
{
|
||||
return this.TitleBarTextNode.Element.innerHTML;
|
||||
}
|
||||
set Title(title: string)
|
||||
{
|
||||
this.TitleBarTextNode.Element.innerHTML = title;
|
||||
}
|
||||
|
||||
// Add all controls to the body of the window
|
||||
get ControlParentNode() : DOM.Node
|
||||
{
|
||||
return this.BodyNode;
|
||||
}
|
||||
|
||||
set ZIndex(z_index: number)
|
||||
{
|
||||
this.Node.ZIndex = z_index;
|
||||
this.SizeLeftNode.ZIndex = z_index + 1;
|
||||
this.SizeRightNode.ZIndex = z_index + 1;
|
||||
this.SizeTopNode.ZIndex = z_index + 1;
|
||||
this.SizeBottomNode.ZIndex = z_index + 1;
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
get ZIndex() : number
|
||||
{
|
||||
return this.Node.ZIndex;
|
||||
}
|
||||
|
||||
private SetSnapRuler(side: Side, position: number)
|
||||
{
|
||||
if (this.SnapRulers[side] == null)
|
||||
{
|
||||
// Create on-demand
|
||||
let orient = (side == Side.Left || side == Side.Right) ? RulerOrient.Vertical : RulerOrient.Horizontal;
|
||||
this.SnapRulers[side] = new Ruler(orient, position);
|
||||
this.SnapRulers[side].Node.Colour = "#FFF";
|
||||
|
||||
// Add to the same parent container as the window for clipping
|
||||
if (this.ParentContainer)
|
||||
this.ParentContainer.Add(this.SnapRulers[side]);
|
||||
|
||||
// Display under all siblings
|
||||
this.SnapRulers[side].SendToBottom();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.SnapRulers[side].SetPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
private RemoveSnapRuler(side: Side)
|
||||
{
|
||||
if (this.SnapRulers[side] != null)
|
||||
{
|
||||
// Remove from the container and clear the remaining reference
|
||||
if (this.ParentContainer)
|
||||
this.ParentContainer.Remove(this.SnapRulers[side]);
|
||||
this.SnapRulers[side] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private RemoveSnapRulers()
|
||||
{
|
||||
this.RemoveSnapRuler(Side.Left);
|
||||
this.RemoveSnapRuler(Side.Right);
|
||||
this.RemoveSnapRuler(Side.Top);
|
||||
this.RemoveSnapRuler(Side.Bottom);
|
||||
}
|
||||
|
||||
private UpdateSnapRuler(side: Side, show: boolean, position: number)
|
||||
{
|
||||
if (show)
|
||||
this.SetSnapRuler(side, position);
|
||||
else
|
||||
this.RemoveSnapRuler(side);
|
||||
}
|
||||
|
||||
private UpdateTLSnapRulers(snap_code: SnapCode)
|
||||
{
|
||||
this.UpdateSnapRuler(Side.Top, (snap_code & SnapCode.Y) != 0, this.TopLeft.y - 3);
|
||||
this.UpdateSnapRuler(Side.Left, (snap_code & SnapCode.X) != 0, this.TopLeft.x - 3);
|
||||
}
|
||||
|
||||
private UpdateBRSnapRulers(snap_code: SnapCode)
|
||||
{
|
||||
this.UpdateSnapRuler(Side.Bottom, (snap_code & SnapCode.Y) != 0, this.BottomRight.y + 1);
|
||||
this.UpdateSnapRuler(Side.Right, (snap_code & SnapCode.X) != 0, this.BottomRight.x + 1);
|
||||
}
|
||||
|
||||
// --- Window movement --------------------------------------------------------------------
|
||||
|
||||
private OnBeginMove(event: Event, mouse_pos: int2)
|
||||
{
|
||||
// Prepare for drag
|
||||
this.DragMouseStartPosition = mouse_pos;
|
||||
this.DragWindowStartPosition = this.Position.Copy();
|
||||
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container)
|
||||
{
|
||||
// Display last snap configuration on initial click
|
||||
let snap_tl = FindSnapControls(parent_container, this.TopLeft, new int2(-1, -1), [ this ]);
|
||||
let snap_br = FindSnapControls(parent_container, this.BottomRight, new int2(1, 1), [ this ]);
|
||||
this.UpdateTLSnapRulers(snap_tl[0]);
|
||||
this.UpdateBRSnapRulers(snap_br[0]);
|
||||
}
|
||||
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
private OnMouseStart = (event: MouseEvent) =>
|
||||
{
|
||||
let mouse_pos = DOM.Event.GetMousePosition(event);
|
||||
this.OnBeginMove(event, mouse_pos);
|
||||
|
||||
// Dynamically add handlers for movement and release
|
||||
$(document).MouseMoveEvent.Subscribe(this.OnMouseMove);
|
||||
$(document).MouseUpEvent.Subscribe(this.OnMouseEnd);
|
||||
}
|
||||
private OnTouchStart = (event: TouchEvent) =>
|
||||
{
|
||||
// Use position of the first touch in the list
|
||||
let touch = event.changedTouches[0];
|
||||
this.ActiveTouchID = touch.identifier;
|
||||
let touch_pos = new int2(touch.pageX, touch.pageY);
|
||||
this.OnBeginMove(event, touch_pos);
|
||||
|
||||
// Dynamically add handlers for movement and release
|
||||
$(document).TouchMoveEvent.Subscribe(this.OnTouchMove);
|
||||
$(document).TouchEndEvent.Subscribe(this.OnTouchEnd);
|
||||
}
|
||||
|
||||
private OnMove(event: Event, mouse_pos: int2)
|
||||
{
|
||||
// Use the offset at the beginning of movement to drag the window around
|
||||
let offset = int2.Sub(mouse_pos, this.DragMouseStartPosition);
|
||||
this.Position = int2.Add(this.DragWindowStartPosition, offset);
|
||||
|
||||
// Snap position of the window to the edges of neighbouring windows
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container != null)
|
||||
{
|
||||
let snap_tl = FindSnapControls(parent_container, this.TopLeft, new int2(-1, -1), [ this ]);
|
||||
if (snap_tl[0] != SnapCode.None)
|
||||
this.Position = snap_tl[1];
|
||||
|
||||
let snap_br = FindSnapControls(parent_container, this.BottomRight, new int2(1, 1), [ this ]);
|
||||
if (snap_br[0] != SnapCode.None)
|
||||
this.Position = int2.Sub(snap_br[1], this.Size);
|
||||
|
||||
this.UpdateTLSnapRulers(snap_tl[0]);
|
||||
this.UpdateBRSnapRulers(snap_br[0]);
|
||||
}
|
||||
|
||||
// TODO: OnMove handler
|
||||
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
private OnMouseMove = (event: MouseEvent) =>
|
||||
{
|
||||
let mouse_pos = DOM.Event.GetMousePosition(event);
|
||||
this.OnMove(event, mouse_pos);
|
||||
}
|
||||
private OnTouchMove = (event: TouchEvent) =>
|
||||
{
|
||||
// Find the currently active touch to update movement
|
||||
for (let i = 0; i < event.changedTouches.length; i++)
|
||||
{
|
||||
let touch = event.changedTouches[i];
|
||||
if (touch.identifier == this.ActiveTouchID)
|
||||
{
|
||||
let touch_pos = new int2(touch.pageX, touch.pageY);
|
||||
this.OnMove(event, touch_pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private OnEndMove(event: Event)
|
||||
{
|
||||
this.RemoveSnapRulers();
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
private OnMouseEnd = (event: Event) =>
|
||||
{
|
||||
this.OnEndMove(event);
|
||||
|
||||
// Remove handlers added during mouse down
|
||||
$(document).MouseMoveEvent.Unsubscribe(this.OnMouseMove);
|
||||
$(document).MouseUpEvent.Unsubscribe(this.OnMouseEnd);
|
||||
}
|
||||
private OnTouchEnd = (event: Event) =>
|
||||
{
|
||||
this.OnEndMove(event);
|
||||
|
||||
// Remove handlers added during touch down
|
||||
$(document).TouchMoveEvent.Unsubscribe(this.OnTouchMove);
|
||||
$(document).TouchEndEvent.Unsubscribe(this.OnTouchEnd);
|
||||
}
|
||||
|
||||
// --- Window sizing ---------------------------------------------------------------------
|
||||
|
||||
private GetSizeMask(mouse_pos: int2) : int2
|
||||
{
|
||||
// Subtract absolute parent node position from the mouse position
|
||||
if (this.ParentNode)
|
||||
mouse_pos = int2.Sub(mouse_pos, this.ParentNode.AbsolutePosition);
|
||||
|
||||
// Use the DOM Node dimensions as they include visible borders/margins
|
||||
let offset_top_left = int2.Sub(mouse_pos, this.TopLeft);
|
||||
let offset_bottom_right = int2.Sub(this.BottomRight, mouse_pos);
|
||||
|
||||
// -1/1 for left/right top/bottom
|
||||
let mask = new int2(0);
|
||||
if (offset_bottom_right.x < this.SideBarSize && offset_bottom_right.x >= 0)
|
||||
mask.x = 1;
|
||||
if (offset_top_left.x < this.SideBarSize && offset_top_left.x >= 0)
|
||||
mask.x = -1;
|
||||
if (offset_bottom_right.y < this.SideBarSize && offset_bottom_right.y >= 0)
|
||||
mask.y = 1;
|
||||
if (offset_top_left.y < this.SideBarSize && offset_top_left.y >= 0)
|
||||
mask.y = -1;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
private SetResizeCursor(node: DOM.Node, size_mask: int2)
|
||||
{
|
||||
// Combine resize directions
|
||||
let cursor = "";
|
||||
if (size_mask.y > 0)
|
||||
cursor += "s";
|
||||
if (size_mask.y < 0)
|
||||
cursor += "n";
|
||||
if (size_mask.x > 0)
|
||||
cursor += "e";
|
||||
if (size_mask.x < 0)
|
||||
cursor += "w";
|
||||
|
||||
// Concat resize ident
|
||||
if (cursor.length > 0)
|
||||
cursor += "-resize";
|
||||
|
||||
node.Cursor = cursor;
|
||||
}
|
||||
|
||||
private RestoreCursor(node: DOM.Node)
|
||||
{
|
||||
node.Cursor = "auto";
|
||||
}
|
||||
|
||||
private OnMoveOverSize = (event: MouseEvent) =>
|
||||
{
|
||||
// Dynamically decide on the mouse cursor
|
||||
let mouse_pos = DOM.Event.GetMousePosition(event);
|
||||
let mask = this.GetSizeMask(mouse_pos);
|
||||
this.SetResizeCursor($(event.target), mask);
|
||||
}
|
||||
|
||||
private MakeControlAABB(control: Control)
|
||||
{
|
||||
// Expand control AABB by snap region to check for snap intersections
|
||||
let aabb = new AABB(control.TopLeft, control.BottomRight);
|
||||
aabb.Expand(Container.SnapBorderSize);
|
||||
return aabb;
|
||||
}
|
||||
|
||||
private TakeConnectedAnchorControls(aabb_0: AABB, anchor_controls: [Control, int2][])
|
||||
{
|
||||
// Search what's left of the anchor controls list for intersecting controls
|
||||
for (let i = 0; i < this.AnchorControls.length; )
|
||||
{
|
||||
let anchor_control = this.AnchorControls[i];
|
||||
let aabb_1 = this.MakeControlAABB(anchor_control[0]);
|
||||
|
||||
if (AABB.Intersect(aabb_0, aabb_1))
|
||||
{
|
||||
// Add to the list of connected controls
|
||||
anchor_controls.push(anchor_control);
|
||||
|
||||
// Swap the control with the back of the array and reduce array count
|
||||
// Faster than a splice for removal (unless the VM detects this)
|
||||
this.AnchorControls[i] = this.AnchorControls[this.AnchorControls.length - 1];
|
||||
this.AnchorControls.length--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only advance when there's no swap as we want to evaluate each
|
||||
// new control swapped in
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MakeAnchorControlIsland()
|
||||
{
|
||||
// TODO: Intersection test doesn't work for overlap test!
|
||||
let anchor_controls: [Control, int2][] = [ ];
|
||||
|
||||
// First find all controls connected to this one
|
||||
let aabb_0 = this.MakeControlAABB(this);
|
||||
this.TakeConnectedAnchorControls(aabb_0, anchor_controls);
|
||||
|
||||
// Then find all controls connected to each of them
|
||||
for (let anchor_control of anchor_controls)
|
||||
{
|
||||
let aabb_0 = this.MakeControlAABB(anchor_control[0]);
|
||||
this.TakeConnectedAnchorControls(aabb_0, anchor_controls);
|
||||
}
|
||||
|
||||
// Replace the anchor control list with only connected controls
|
||||
this.AnchorControls = anchor_controls;
|
||||
}
|
||||
|
||||
private GatherAnchorControls(mask: int2, gather_sibling_controls: boolean)
|
||||
{
|
||||
// Reset list just in case end event isn't received
|
||||
this.AnchorControls = [];
|
||||
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container)
|
||||
{
|
||||
// Rebuild the connectivity graph for the parent
|
||||
let control_graph = parent_container.ControlGraph;
|
||||
control_graph.Build(parent_container);
|
||||
|
||||
// Iterate all references on all sides
|
||||
let control_index = parent_container.Controls.indexOf(this);
|
||||
for (let side = 0; side < 4; side++)
|
||||
{
|
||||
let ref_info = control_graph.RefInfos[control_index * 4 + side];
|
||||
for (let ref_index = 0; ref_index < ref_info.NbRefs; ref_index++)
|
||||
{
|
||||
let ref = ref_info.GetControlRef(ref_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parent_container && gather_sibling_controls)
|
||||
{
|
||||
// Gather auto-anchor controls from siblings on side resizers only
|
||||
if ((mask.x != 0) != (mask.y != 0))
|
||||
{
|
||||
if (mask.x > 0 || mask.y > 0)
|
||||
WM.FindSnapControls(parent_container, this.BottomRight, mask, [ this ], this.AnchorControls);
|
||||
if (mask.x < 0 || mask.y < 0)
|
||||
WM.FindSnapControls(parent_container, this.TopLeft, mask, [ this ], this.AnchorControls);
|
||||
}
|
||||
|
||||
// We don't want windows at disjoint locations getting dragged into
|
||||
// the auto anchor so only allow those connected by existing snap
|
||||
// boundaries
|
||||
this.MakeAnchorControlIsland();
|
||||
}
|
||||
}
|
||||
|
||||
private OnBeginSize = (event: MouseEvent, in_mask: int2, master_control: boolean) =>
|
||||
{
|
||||
let mouse_pos = DOM.Event.GetMousePosition(event);
|
||||
|
||||
// Prepare for drag
|
||||
this.DragMouseStartPosition = mouse_pos;
|
||||
this.DragWindowStartPosition = this.Position.Copy();
|
||||
this.DragWindowStartSize = this.Size.Copy();
|
||||
|
||||
let mask = in_mask || this.GetSizeMask(mouse_pos);
|
||||
|
||||
// Start resizing gathered auto-anchors
|
||||
this.GatherAnchorControls(mask, master_control);
|
||||
for (let control of this.AnchorControls)
|
||||
{
|
||||
let window = control[0] as Window;
|
||||
if (window != null)
|
||||
window.OnBeginSize(event, control[1], false);
|
||||
}
|
||||
|
||||
// Build a control graph for the children
|
||||
// TODO: Do this always; it has to be recursive
|
||||
// TODO: Only Build
|
||||
// TODO: Move all this into Container
|
||||
this.ControlGraph.Build(this);
|
||||
this.ControlSizerX.Build(Side.Left, this, this.ControlGraph);
|
||||
this.ControlSizerY.Build(Side.Top, this, this.ControlGraph);
|
||||
|
||||
this.SizerMoved = false;
|
||||
|
||||
if (master_control)
|
||||
{
|
||||
// Display initial snap rulers
|
||||
if (mask.x > 0 || mask.y > 0)
|
||||
{
|
||||
let snap = FindSnapControls(this.ParentContainer, this.BottomRight, mask, [ this ]);
|
||||
this.UpdateBRSnapRulers(snap[0]);
|
||||
}
|
||||
if (mask.x < 0 || mask.y < 0)
|
||||
{
|
||||
let snap = FindSnapControls(this.ParentContainer, this.TopLeft, mask, [ this ]);
|
||||
this.UpdateTLSnapRulers(snap[0]);
|
||||
}
|
||||
|
||||
// If the sizer is held and not moved for a period, release all anchored controls
|
||||
// so that it can be independently moved
|
||||
setTimeout( () =>
|
||||
{
|
||||
if (this.SizerMoved == false)
|
||||
{
|
||||
this.AnchorControls = [ ];
|
||||
this.RemoveSnapRulers();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Dynamically add handlers for movement and release
|
||||
this.OnSizeDelegate = (event: MouseEvent) => { this.OnSize(event, mask, null); };
|
||||
this.OnEndSizeDelegate = (event: MouseEvent) => { this.OnEndSize(event, mask); };
|
||||
$(document).MouseMoveEvent.Subscribe(this.OnSizeDelegate);
|
||||
$(document).MouseUpEvent.Subscribe(this.OnEndSizeDelegate);
|
||||
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
}
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
|
||||
private OnSize = (event: MouseEvent, mask: int2, master_offset: int2) =>
|
||||
{
|
||||
// Use the offset from the mouse start position to drag the edge around
|
||||
let mouse_pos = DOM.Event.GetMousePosition(event);
|
||||
let offset = master_offset || int2.Sub(mouse_pos, this.DragMouseStartPosition);
|
||||
|
||||
// Chrome issues multiple redundant OnSize events even if the mouse is held still
|
||||
// Ignore those by checking for no initial mouse movement
|
||||
if (this.SizerMoved == false && offset.x == 0 && offset.y == 0)
|
||||
{
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
return;
|
||||
}
|
||||
this.SizerMoved = true;
|
||||
|
||||
// Size goes left/right with mask
|
||||
this.Size = int2.Add(this.DragWindowStartSize, int2.Mul(offset, mask));
|
||||
|
||||
// Position stays put or drifts right with mask
|
||||
let position_mask = int2.Min0(mask);
|
||||
this.Position = int2.Sub(this.DragWindowStartPosition, int2.Mul(offset, position_mask));
|
||||
|
||||
// Build up a list of controls to exclude from snapping
|
||||
// Don't snap anchor controls as they'll already be dragged around with this size event
|
||||
let exclude_controls: [Control] = [ this ];
|
||||
for (let anchor of this.AnchorControls)
|
||||
exclude_controls.push(anchor[0]);
|
||||
|
||||
// Snap edges to neighbouring edges in the parent container
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container != null)
|
||||
{
|
||||
if (mask.x > 0 || mask.y > 0)
|
||||
{
|
||||
let snap = FindSnapControls(parent_container, this.BottomRight, mask, exclude_controls);
|
||||
if (snap[0] != SnapCode.None)
|
||||
{
|
||||
// Adjust offset to allow anchored controls to match the snap motions
|
||||
offset = int2.Add(offset, int2.Sub(snap[1], this.BottomRight));
|
||||
|
||||
this.BottomRight = snap[1];
|
||||
}
|
||||
|
||||
// Only display ruler for master control
|
||||
if (master_offset == null)
|
||||
this.UpdateBRSnapRulers(snap[0]);
|
||||
}
|
||||
if (mask.x < 0 || mask.y < 0)
|
||||
{
|
||||
let snap = FindSnapControls(parent_container, this.TopLeft, mask, exclude_controls);
|
||||
if (snap[0] != SnapCode.None)
|
||||
{
|
||||
// Adjust offset to allow anchored controls to match the snap motions
|
||||
offset = int2.Add(offset, int2.Sub(snap[1], this.TopLeft));
|
||||
|
||||
this.TopLeft = snap[1];
|
||||
}
|
||||
|
||||
// Only display ruler for master control
|
||||
if (master_offset == null)
|
||||
this.UpdateTLSnapRulers(snap[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ControlGraph)
|
||||
{
|
||||
this.ControlSizerX.ChangeSize(this.ControlParentNode.Size.x, this.ControlGraph);
|
||||
this.ControlSizerY.ChangeSize(this.ControlParentNode.Size.y, this.ControlGraph);
|
||||
}
|
||||
|
||||
// Clamp window size to a minimum
|
||||
let min_window_size = new int2(50);
|
||||
this.Size = int2.Max(this.Size, min_window_size);
|
||||
this.Position = int2.Min(this.Position, int2.Sub(int2.Add(this.DragWindowStartPosition, this.DragWindowStartSize), min_window_size));
|
||||
|
||||
// Resize all anchored controls
|
||||
for (let control of this.AnchorControls)
|
||||
{
|
||||
let window = control[0] as Window;
|
||||
if (window != null)
|
||||
window.OnSize(event, control[1], offset);
|
||||
}
|
||||
|
||||
// The cursor will exceed the bounds of the resize element under sizing so
|
||||
// force it to whatever it needs to be here
|
||||
this.SetResizeCursor($(document.body), mask);
|
||||
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
private OnEndSize = (event: MouseEvent, mask: int2) =>
|
||||
{
|
||||
// End all anchored controls
|
||||
for (let control of this.AnchorControls)
|
||||
{
|
||||
let window = control[0] as Window;
|
||||
if (window != null)
|
||||
window.OnEndSize(event, mask);
|
||||
}
|
||||
|
||||
// Clear anchor references so they don't hang around if a window is deleted
|
||||
this.AnchorControls = [];
|
||||
|
||||
// Set cursor back to auto
|
||||
this.RestoreCursor($(document.body));
|
||||
|
||||
this.RemoveSnapRulers();
|
||||
|
||||
// Remove handlers added during mouse down
|
||||
$(document).MouseMoveEvent.Unsubscribe(this.OnSizeDelegate);
|
||||
this.OnSizeDelegate = null;
|
||||
$(document).MouseUpEvent.Unsubscribe(this.OnEndSizeDelegate);
|
||||
this.OnEndSizeDelegate = null;
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
|
||||
private UpdateDebugText()
|
||||
{
|
||||
let text = "";
|
||||
text += this.BottomRight.x;
|
||||
this.DebugNode.SetText(text);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
//
|
||||
// How I configured this project:
|
||||
//
|
||||
// 1. Download and install TypeScript for Visual Studio 2015 (https://www.microsoft.com/en-us/download/details.aspx?id=48593)
|
||||
// 2. Setup "typescript.tsdk" to point to "C:\\Program Files (x86)\\Microsoft SDKs\\TypeScript\\2.0\\"
|
||||
// 3. Copy https://raw.githubusercontent.com/Microsoft/TypeScript/release-2.0/lib/tsserver.js to the TypeScript directory.
|
||||
//
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "amd",
|
||||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"outFile": "remotery.js",
|
||||
"sourceMap": true
|
||||
},
|
||||
"files": [
|
||||
"Math.ts",
|
||||
"Hash.ts",
|
||||
"DOM.ts",
|
||||
"DOM_Node.ts",
|
||||
"WM_Panel.ts",
|
||||
"WM_PanelContainer.ts",
|
||||
"WM_PanelGraph.ts",
|
||||
"WM_PanelSizer.ts",
|
||||
"WM_Control.ts",
|
||||
"WM_Ruler.ts",
|
||||
"WM_Container.ts",
|
||||
"WM_ControlGraph.ts",
|
||||
"WM_ControlSizer.ts",
|
||||
"WM_ControlSnapper.ts",
|
||||
"WM_Window.ts",
|
||||
"WM_Serialisation.ts",
|
||||
"Test.ts"
|
||||
]
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
|
||||
Console = (function()
|
||||
{
|
||||
var BORDER = 10;
|
||||
var HEIGHT = 200;
|
||||
|
||||
|
||||
function Console(wm, server)
|
||||
{
|
||||
// Create the window and its controls
|
||||
this.Window = wm.AddWindow("Console", 10, 10, 100, 100);
|
||||
this.PageContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160));
|
||||
DOM.Node.AddClass(this.PageContainer.Node, "ConsoleText");
|
||||
this.AppContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160));
|
||||
DOM.Node.AddClass(this.AppContainer.Node, "ConsoleText");
|
||||
this.UserInput = this.Window.AddControlNew(new WM.EditBox(10, 5, 400, 30, "Input", ""));
|
||||
this.UserInput.SetChangeHandler(Bind(ProcessInput, this));
|
||||
this.Window.ShowNoAnim();
|
||||
|
||||
// This accumulates log text as fast as is required
|
||||
this.PageTextBuffer = "";
|
||||
this.PageTextUpdatePending = false;
|
||||
this.AppTextBuffer = "";
|
||||
this.AppTextUpdatePending = false;
|
||||
|
||||
// Setup command history control
|
||||
this.CommandHistory = LocalStore.Get("App", "Global", "CommandHistory", [ ]);
|
||||
this.CommandIndex = 0;
|
||||
this.MaxNbCommands = 200;
|
||||
DOM.Event.AddHandler(this.UserInput.EditNode, "keydown", Bind(OnKeyPress, this));
|
||||
DOM.Event.AddHandler(this.UserInput.EditNode, "focus", Bind(OnFocus, this));
|
||||
|
||||
// At a much lower frequency this will update the console window
|
||||
window.setInterval(Bind(UpdateHTML, this), 500);
|
||||
|
||||
// Setup log requests from the server
|
||||
this.Server = server;
|
||||
server.SetConsole(this);
|
||||
server.AddMessageHandler("LOGM", Bind(OnLog, this));
|
||||
|
||||
this.Window.SetOnResize(Bind(OnUserResize, this));
|
||||
}
|
||||
|
||||
|
||||
Console.prototype.Log = function(text)
|
||||
{
|
||||
this.PageTextBuffer = LogText(this.PageTextBuffer, text);
|
||||
this.PageTextUpdatePending = true;
|
||||
}
|
||||
|
||||
|
||||
Console.prototype.WindowResized = function(width, height)
|
||||
{
|
||||
// Place window
|
||||
this.Window.SetPosition(BORDER, height - BORDER - 200);
|
||||
this.Window.SetSize(width - 2 * BORDER, HEIGHT);
|
||||
|
||||
ResizeInternals(this);
|
||||
}
|
||||
|
||||
|
||||
function OnLog(self, socket, data_view)
|
||||
{
|
||||
var data_view_reader = new DataViewReader(data_view, 4);
|
||||
var text = data_view_reader.GetString();
|
||||
self.AppTextBuffer = LogText(self.AppTextBuffer, text);
|
||||
self.AppTextUpdatePending = true;
|
||||
}
|
||||
|
||||
|
||||
function LogText(existing_text, new_text)
|
||||
{
|
||||
// Filter the text a little to make it safer
|
||||
if (new_text == null)
|
||||
new_text = "NULL";
|
||||
|
||||
// Find and convert any HTML entities, ensuring the browser doesn't parse any embedded HTML code
|
||||
// This also allows the log to contain arbitrary C++ code (e.g. assert comparison operators)
|
||||
new_text = Convert.string_to_html_entities(new_text);
|
||||
|
||||
// Prefix date and end with new line
|
||||
var d = new Date();
|
||||
new_text = "[" + d.toLocaleTimeString() + "] " + new_text + "<br>";
|
||||
|
||||
// Append to local text buffer and ensure clip the oldest text to ensure a max size
|
||||
existing_text = existing_text + new_text;
|
||||
var max_len = 10 * 1024;
|
||||
var len = existing_text.length;
|
||||
if (len > max_len)
|
||||
existing_text = existing_text.substr(len - max_len, max_len);
|
||||
|
||||
return existing_text;
|
||||
}
|
||||
|
||||
function OnUserResize(self, evt)
|
||||
{
|
||||
ResizeInternals(self);
|
||||
}
|
||||
|
||||
function ResizeInternals(self)
|
||||
{
|
||||
// Place controls
|
||||
var parent_size = self.Window.Size;
|
||||
var mid_w = parent_size[0] / 3;
|
||||
self.UserInput.SetPosition(BORDER, parent_size[1] - 2 * BORDER - 30);
|
||||
self.UserInput.SetSize(parent_size[0] - 100, 18);
|
||||
var output_height = self.UserInput.Position[1] - 2 * BORDER;
|
||||
self.PageContainer.SetPosition(BORDER, BORDER);
|
||||
self.PageContainer.SetSize(mid_w - 2 * BORDER, output_height);
|
||||
self.AppContainer.SetPosition(mid_w, BORDER);
|
||||
self.AppContainer.SetSize(parent_size[0] - mid_w - BORDER, output_height);
|
||||
}
|
||||
|
||||
|
||||
function UpdateHTML(self)
|
||||
{
|
||||
// Reset the current text buffer as html
|
||||
|
||||
if (self.PageTextUpdatePending)
|
||||
{
|
||||
var page_node = self.PageContainer.Node;
|
||||
page_node.innerHTML = self.PageTextBuffer;
|
||||
page_node.scrollTop = page_node.scrollHeight;
|
||||
self.PageTextUpdatePending = false;
|
||||
}
|
||||
|
||||
if (self.AppTextUpdatePending)
|
||||
{
|
||||
var app_node = self.AppContainer.Node;
|
||||
app_node.innerHTML = self.AppTextBuffer;
|
||||
app_node.scrollTop = app_node.scrollHeight;
|
||||
self.AppTextUpdatePending = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ProcessInput(self, node)
|
||||
{
|
||||
// Send the message exactly
|
||||
var msg = node.value;
|
||||
self.Server.Send("CONI" + msg);
|
||||
|
||||
// Emit to console and clear
|
||||
self.Log("> " + msg);
|
||||
self.UserInput.SetValue("");
|
||||
|
||||
// Keep track of recently issued commands, with an upper bound
|
||||
self.CommandHistory.push(msg);
|
||||
var extra_commands = self.CommandHistory.length - self.MaxNbCommands;
|
||||
if (extra_commands > 0)
|
||||
self.CommandHistory.splice(0, extra_commands);
|
||||
|
||||
// Set command history index to the most recent command
|
||||
self.CommandIndex = self.CommandHistory.length;
|
||||
|
||||
// Backup to local store
|
||||
LocalStore.Set("App", "Global", "CommandHistory", self.CommandHistory);
|
||||
|
||||
// Keep focus with the edit box
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function OnKeyPress(self, evt)
|
||||
{
|
||||
evt = DOM.Event.Get(evt);
|
||||
|
||||
if (evt.keyCode == Keyboard.Codes.UP)
|
||||
{
|
||||
if (self.CommandHistory.length > 0)
|
||||
{
|
||||
// Cycle backwards through the command history
|
||||
self.CommandIndex--;
|
||||
if (self.CommandIndex < 0)
|
||||
self.CommandIndex = self.CommandHistory.length - 1;
|
||||
var command = self.CommandHistory[self.CommandIndex];
|
||||
self.UserInput.SetValue(command);
|
||||
}
|
||||
|
||||
// Stops default behaviour of moving cursor to the beginning
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
|
||||
else if (evt.keyCode == Keyboard.Codes.DOWN)
|
||||
{
|
||||
if (self.CommandHistory.length > 0)
|
||||
{
|
||||
// Cycle fowards through the command history
|
||||
self.CommandIndex = (self.CommandIndex + 1) % self.CommandHistory.length;
|
||||
var command = self.CommandHistory[self.CommandIndex];
|
||||
self.UserInput.SetValue(command);
|
||||
}
|
||||
|
||||
// Stops default behaviour of moving cursor to the end
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnFocus(self)
|
||||
{
|
||||
// Reset command index on focus
|
||||
self.CommandIndex = self.CommandHistory.length;
|
||||
}
|
||||
|
||||
|
||||
return Console;
|
||||
})();
|
|
@ -1,47 +0,0 @@
|
|||
|
||||
//
|
||||
// Simple wrapper around DataView that auto-advances the read offset and provides
|
||||
// a few common data type conversions specific to this app
|
||||
//
|
||||
DataViewReader = (function ()
|
||||
{
|
||||
function DataViewReader(data_view, offset)
|
||||
{
|
||||
this.DataView = data_view;
|
||||
this.Offset = offset;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetUInt32 = function ()
|
||||
{
|
||||
var v = this.DataView.getUint32(this.Offset, true);
|
||||
this.Offset += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetUInt64 = function ()
|
||||
{
|
||||
var v = this.DataView.getFloat64(this.Offset, true);
|
||||
this.Offset += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetStringOfLength = function (string_length)
|
||||
{
|
||||
var string = "";
|
||||
for (var i = 0; i < string_length; i++)
|
||||
{
|
||||
string += String.fromCharCode(this.DataView.getInt8(this.Offset));
|
||||
this.Offset++;
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetString = function ()
|
||||
{
|
||||
var string_length = this.GetUInt32();
|
||||
return this.GetStringOfLength(string_length);
|
||||
}
|
||||
|
||||
return DataViewReader;
|
||||
})();
|
|
@ -1,61 +0,0 @@
|
|||
|
||||
|
||||
PixelTimeRange = (function()
|
||||
{
|
||||
function PixelTimeRange(start_us, span_us, span_px)
|
||||
{
|
||||
this.Span_px = span_px;
|
||||
this.Set(start_us, span_us);
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.Set = function(start_us, span_us)
|
||||
{
|
||||
this.Start_us = start_us;
|
||||
this.Span_us = span_us;
|
||||
this.End_us = this.Start_us + span_us;
|
||||
this.usPerPixel = this.Span_px / this.Span_us;
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.SetStart = function(start_us)
|
||||
{
|
||||
this.Start_us = start_us;
|
||||
this.End_us = start_us + this.Span_us;
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.SetEnd = function(end_us)
|
||||
{
|
||||
this.End_us = end_us;
|
||||
this.Start_us = end_us - this.Span_us;
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.SetPixelSpan = function(span_px)
|
||||
{
|
||||
this.Span_px = span_px;
|
||||
this.usPerPixel = this.Span_px / this.Span_us;
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.PixelOffset = function(time_us)
|
||||
{
|
||||
return Math.floor((time_us - this.Start_us) * this.usPerPixel);
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.PixelSize = function(time_us)
|
||||
{
|
||||
return Math.floor(time_us * this.usPerPixel);
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.Clone = function()
|
||||
{
|
||||
return new PixelTimeRange(this.Start_us, this.Span_us, this.Span_px);
|
||||
}
|
||||
|
||||
|
||||
return PixelTimeRange;
|
||||
})();
|
|
@ -1,338 +0,0 @@
|
|||
|
||||
//
|
||||
// TODO: Window resizing needs finer-grain control
|
||||
// TODO: Take into account where user has moved the windows
|
||||
// TODO: Controls need automatic resizing within their parent windows
|
||||
//
|
||||
|
||||
|
||||
Settings = (function()
|
||||
{
|
||||
function Settings()
|
||||
{
|
||||
this.IsPaused = false;
|
||||
}
|
||||
|
||||
return Settings;
|
||||
|
||||
})();
|
||||
|
||||
|
||||
Remotery = (function()
|
||||
{
|
||||
// crack the url and get the parameter we want
|
||||
var getUrlParameter = function getUrlParameter( search_param)
|
||||
{
|
||||
var page_url = decodeURIComponent( window.location.search.substring(1) ),
|
||||
url_vars = page_url.split('&'),
|
||||
param_name,
|
||||
i;
|
||||
|
||||
for (i = 0; i < url_vars.length; i++)
|
||||
{
|
||||
param_name = url_vars[i].split('=');
|
||||
|
||||
if (param_name[0] === search_param)
|
||||
{
|
||||
return param_name[1] === undefined ? true : param_name[1];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function Remotery()
|
||||
{
|
||||
this.WindowManager = new WM.WindowManager();
|
||||
this.Settings = new Settings();
|
||||
|
||||
// "addr" param is ip:port and will override the local store version if passed in the URL
|
||||
var addr = getUrlParameter( "addr" );
|
||||
if ( addr != null )
|
||||
this.ConnectionAddress = "ws://" + addr + "/rmt";
|
||||
else
|
||||
this.ConnectionAddress = LocalStore.Get("App", "Global", "ConnectionAddress", "ws://127.0.0.1:17815/rmt");
|
||||
|
||||
this.Server = new WebSocketConnection();
|
||||
this.Server.AddConnectHandler(Bind(OnConnect, this));
|
||||
|
||||
// Create the console up front as everything reports to it
|
||||
this.Console = new Console(this.WindowManager, this.Server);
|
||||
|
||||
// Create required windows
|
||||
this.TitleWindow = new TitleWindow(this.WindowManager, this.Settings, this.Server, this.ConnectionAddress);
|
||||
this.TitleWindow.SetConnectionAddressChanged(Bind(OnAddressChanged, this));
|
||||
this.TimelineWindow = new TimelineWindow(this.WindowManager, this.Settings, this.Server, Bind(OnTimelineCheck, this));
|
||||
this.TimelineWindow.SetOnHover(Bind(OnSampleHover, this));
|
||||
this.TimelineWindow.SetOnSelected(Bind(OnSampleSelected, this));
|
||||
|
||||
this.NbSampleWindows = 0;
|
||||
this.SampleWindows = { };
|
||||
this.FrameHistory = { };
|
||||
this.SelectedFrames = { };
|
||||
this.NameMap = { };
|
||||
|
||||
this.Server.AddMessageHandler("SMPL", Bind(OnSamples, this));
|
||||
this.Server.AddMessageHandler("SSMP", Bind(OnSampleName, this));
|
||||
|
||||
// Kick-off the auto-connect loop
|
||||
AutoConnect(this);
|
||||
|
||||
// Hook up resize event handler
|
||||
DOM.Event.AddHandler(window, "resize", Bind(OnResizeWindow, this));
|
||||
OnResizeWindow(this);
|
||||
|
||||
// Hook up browser-native canvas refresh
|
||||
this.DisplayFrame = 0;
|
||||
this.LastKnownPause = this.Settings.IsPaused;
|
||||
var self = this;
|
||||
(function display_loop()
|
||||
{
|
||||
window.requestAnimationFrame(display_loop);
|
||||
DrawTimeline(self);
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
function AutoConnect(self)
|
||||
{
|
||||
// Only attempt to connect if there isn't already a connection or an attempt to connect
|
||||
if (!self.Server.Connected())
|
||||
self.Server.Connect(self.ConnectionAddress);
|
||||
|
||||
// Always schedule another check
|
||||
window.setTimeout(Bind(AutoConnect, self), 2000);
|
||||
}
|
||||
|
||||
|
||||
function OnConnect(self)
|
||||
{
|
||||
// Connection address has been validated
|
||||
LocalStore.Set("App", "Global", "ConnectionAddress", self.ConnectionAddress);
|
||||
}
|
||||
|
||||
|
||||
function OnAddressChanged(self, node)
|
||||
{
|
||||
// Update and disconnect, relying on auto-connect to reconnect
|
||||
self.ConnectionAddress = node.value;
|
||||
self.Server.Disconnect();
|
||||
|
||||
// Give input focus away
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function DrawTimeline(self)
|
||||
{
|
||||
// Has pause state changed?
|
||||
if (self.Settings.IsPaused != self.LastKnownPaused)
|
||||
{
|
||||
// When switching TO paused, draw one last frame to ensure the sample text gets drawn
|
||||
self.LastKnownPaused = self.Settings.IsPaused;
|
||||
self.TimelineWindow.DrawAllRows();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't waste time drawing the timeline when paused
|
||||
if (self.Settings.IsPaused)
|
||||
return;
|
||||
|
||||
// requestAnimationFrame can run up to 60hz which is way too much for drawing the timeline
|
||||
// Assume it's running at 60hz and skip frames to achieve 10hz instead
|
||||
// Doing this instead of using setTimeout because it's better for browser rendering (or; will be once WebGL is in use)
|
||||
// TODO: Expose as config variable because high refresh rate is great when using a separate viewiing machine
|
||||
if ((self.DisplayFrame % 10) == 0)
|
||||
self.TimelineWindow.DrawAllRows();
|
||||
|
||||
self.DisplayFrame++;
|
||||
}
|
||||
|
||||
|
||||
function DecodeSample(self, data_view_reader)
|
||||
{
|
||||
var sample = {};
|
||||
|
||||
// Get name hash and lookup name it map
|
||||
sample.name_hash = data_view_reader.GetUInt32();
|
||||
sample.name = self.NameMap[sample.name_hash];
|
||||
|
||||
// If the name doesn't exist in the map yet, request it from the server
|
||||
if (sample.name == undefined)
|
||||
{
|
||||
// Meanwhile, store the hash as the name
|
||||
sample.name = { "string": sample.name_hash };
|
||||
self.NameMap[sample.name_hash] = sample.name;
|
||||
self.Server.Send("GSMP" + sample.name_hash);
|
||||
}
|
||||
|
||||
// Get the rest of the sample data
|
||||
sample.id = data_view_reader.GetUInt32();
|
||||
sample.colour = data_view_reader.GetStringOfLength(7);
|
||||
sample.us_start = data_view_reader.GetUInt64();
|
||||
sample.us_length = data_view_reader.GetUInt64();
|
||||
sample.us_self = data_view_reader.GetUInt64();
|
||||
sample.call_count = data_view_reader.GetUInt32();
|
||||
sample.recurse_depth = data_view_reader.GetUInt32();
|
||||
|
||||
// Calculate dependent properties
|
||||
sample.ms_length = (sample.us_length / 1000.0).toFixed(3);
|
||||
sample.ms_self = (sample.us_self / 1000.0).toFixed(3);
|
||||
|
||||
// Recurse into children
|
||||
sample.children = [];
|
||||
DecodeSampleArray(self, data_view_reader, sample.children);
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
|
||||
function DecodeSampleArray(self, data_view_reader, samples)
|
||||
{
|
||||
var nb_samples = data_view_reader.GetUInt32();
|
||||
for (var i = 0; i < nb_samples; i++)
|
||||
{
|
||||
var sample = DecodeSample(self, data_view_reader);
|
||||
samples.push(sample)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function DecodeSamples(self, data_view_reader)
|
||||
{
|
||||
// Message-specific header
|
||||
var message = { };
|
||||
message.thread_name = data_view_reader.GetString();
|
||||
message.nb_samples = data_view_reader.GetUInt32();
|
||||
message.sample_digest = data_view_reader.GetUInt32();
|
||||
|
||||
// Read samples
|
||||
message.samples = [];
|
||||
message.samples.push(DecodeSample(self, data_view_reader));
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
function OnSamples(self, socket, data_view)
|
||||
{
|
||||
// Discard any new samples while paused
|
||||
if (self.Settings.IsPaused)
|
||||
return;
|
||||
|
||||
// Binary decode incoming sample data
|
||||
var message = DecodeSamples(self, new DataViewReader(data_view, 8));
|
||||
var name = message.thread_name;
|
||||
|
||||
// Add to frame history for this thread
|
||||
var thread_frame = new ThreadFrame(message);
|
||||
if (!(name in self.FrameHistory))
|
||||
self.FrameHistory[name] = [ ];
|
||||
var frame_history = self.FrameHistory[name];
|
||||
frame_history.push(thread_frame);
|
||||
|
||||
// Discard old frames to keep memory-use constant
|
||||
var max_nb_frames = 10000;
|
||||
var extra_frames = frame_history.length - max_nb_frames;
|
||||
if (extra_frames > 0)
|
||||
frame_history.splice(0, extra_frames);
|
||||
|
||||
// Create sample windows on-demand
|
||||
if (!(name in self.SampleWindows))
|
||||
{
|
||||
self.SampleWindows[name] = new SampleWindow(self.WindowManager, name, self.NbSampleWindows);
|
||||
self.SampleWindows[name].WindowResized(self.TimelineWindow.Window, self.Console.Window);
|
||||
self.NbSampleWindows++;
|
||||
MoveSampleWindows(this);
|
||||
}
|
||||
|
||||
// Set on the window and timeline
|
||||
self.SampleWindows[name].OnSamples(message.nb_samples, message.sample_digest, message.samples);
|
||||
self.TimelineWindow.OnSamples(name, frame_history);
|
||||
}
|
||||
|
||||
|
||||
function OnSampleName(self, socket, data_view)
|
||||
{
|
||||
// Add any names sent by the server to the local map
|
||||
var data_view_reader = new DataViewReader(data_view, 4);
|
||||
var name_hash = data_view_reader.GetUInt32();
|
||||
var name = data_view_reader.GetString();
|
||||
self.NameMap[name_hash].string = name;
|
||||
}
|
||||
|
||||
|
||||
function OnTimelineCheck(self, name, evt)
|
||||
{
|
||||
// Show/hide the equivalent sample window and move all the others to occupy any left-over space
|
||||
var target = DOM.Event.GetNode(evt);
|
||||
self.SampleWindows[name].SetVisible(target.checked);
|
||||
MoveSampleWindows(self);
|
||||
}
|
||||
|
||||
|
||||
function MoveSampleWindows(self)
|
||||
{
|
||||
// Stack all windows next to each other
|
||||
var xpos = 0;
|
||||
for (var i in self.SampleWindows)
|
||||
{
|
||||
var sample_window = self.SampleWindows[i];
|
||||
if (sample_window.Visible)
|
||||
sample_window.SetXPos(xpos++, self.TimelineWindow.Window, self.Console.Window);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnSampleHover(self, thread_name, hover)
|
||||
{
|
||||
// Hover only changes sample window contents when paused
|
||||
var sample_window = self.SampleWindows[thread_name];
|
||||
if (sample_window && self.Settings.IsPaused)
|
||||
{
|
||||
if (hover == null)
|
||||
{
|
||||
// When there's no hover, go back to the selected frame
|
||||
if (self.SelectedFrames[thread_name])
|
||||
{
|
||||
var frame = self.SelectedFrames[thread_name];
|
||||
sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Populate with sample under hover
|
||||
var frame = hover[0];
|
||||
sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnSampleSelected(self, thread_name, select)
|
||||
{
|
||||
// Lookup sample window set the frame samples on it
|
||||
if (select && thread_name in self.SampleWindows)
|
||||
{
|
||||
var sample_window = self.SampleWindows[thread_name];
|
||||
var frame = select[0];
|
||||
self.SelectedFrames[thread_name] = frame;
|
||||
sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnResizeWindow(self)
|
||||
{
|
||||
// Resize windows
|
||||
var w = window.innerWidth;
|
||||
var h = window.innerHeight;
|
||||
self.Console.WindowResized(w, h);
|
||||
self.TitleWindow.WindowResized(w, h);
|
||||
self.TimelineWindow.WindowResized(w, h, self.TitleWindow.Window);
|
||||
for (var i in self.SampleWindows)
|
||||
self.SampleWindows[i].WindowResized(self.TimelineWindow.Window, self.Console.Window);
|
||||
}
|
||||
|
||||
|
||||
return Remotery;
|
||||
})();
|
|
@ -1,215 +0,0 @@
|
|||
|
||||
SampleWindow = (function()
|
||||
{
|
||||
function SampleWindow(wm, name, offset)
|
||||
{
|
||||
// Sample digest for checking if grid needs to be repopulated
|
||||
this.NbSamples = 0;
|
||||
this.SampleDigest = null;
|
||||
|
||||
// Source sample reference to reduce repopulation
|
||||
this.Samples = null;
|
||||
|
||||
this.XPos = 10 + offset * 410;
|
||||
this.Window = wm.AddWindow(name, 100, 100, 100, 100);
|
||||
this.Window.ShowNoAnim();
|
||||
this.Visible = true;
|
||||
|
||||
// Create a grid that's indexed by the unique sample ID
|
||||
this.Grid = this.Window.AddControlNew(new WM.Grid());
|
||||
var cell_data =
|
||||
{
|
||||
Name: "Samples",
|
||||
Length: "Time (ms)",
|
||||
Self: "Self (ms)",
|
||||
Calls: "Calls",
|
||||
Recurse: "Recurse",
|
||||
};
|
||||
var cell_classes =
|
||||
{
|
||||
Name: "SampleTitleNameCell",
|
||||
Length: "SampleTitleTimeCell",
|
||||
Self: "SampleTitleTimeCell",
|
||||
Calls: "SampleTitleCountCell",
|
||||
Recurse: "SampleTitleCountCell",
|
||||
};
|
||||
this.RootRow = this.Grid.Rows.Add(cell_data, "GridGroup", cell_classes);
|
||||
this.RootRow.Rows.AddIndex("_ID");
|
||||
}
|
||||
|
||||
|
||||
SampleWindow.prototype.SetXPos = function(xpos, top_window, bottom_window)
|
||||
{
|
||||
Anim.Animate(
|
||||
Bind(AnimatedMove, this, top_window, bottom_window),
|
||||
this.XPos, 10 + xpos * 410, 0.25);
|
||||
}
|
||||
|
||||
|
||||
function AnimatedMove(self, top_window, bottom_window, val)
|
||||
{
|
||||
self.XPos = val;
|
||||
self.WindowResized(top_window, bottom_window);
|
||||
}
|
||||
|
||||
|
||||
SampleWindow.prototype.SetVisible = function(visible)
|
||||
{
|
||||
if (visible != this.Visible)
|
||||
{
|
||||
if (visible == true)
|
||||
this.Window.Show();
|
||||
else
|
||||
this.Window.Hide();
|
||||
|
||||
this.Visible = visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SampleWindow.prototype.WindowResized = function(top_window, bottom_window)
|
||||
{
|
||||
var top = top_window.Position[1] + top_window.Size[1] + 10;
|
||||
this.Window.SetPosition(this.XPos, top_window.Position[1] + top_window.Size[1] + 10);
|
||||
this.Window.SetSize(400, bottom_window.Position[1] - 10 - top);
|
||||
}
|
||||
|
||||
|
||||
SampleWindow.prototype.OnSamples = function(nb_samples, sample_digest, samples)
|
||||
{
|
||||
if (!this.Visible)
|
||||
return;
|
||||
|
||||
// If the source hasn't changed, don't repopulate
|
||||
if (this.Samples == samples)
|
||||
return;
|
||||
this.Samples = samples;
|
||||
|
||||
// Recreate all the HTML if the number of samples gets bigger
|
||||
if (nb_samples > this.NbSamples)
|
||||
{
|
||||
GrowGrid(this.RootRow, nb_samples);
|
||||
this.NbSamples = nb_samples;
|
||||
}
|
||||
|
||||
// If the content of the samples changes from previous update, update them all
|
||||
if (this.SampleDigest != sample_digest)
|
||||
{
|
||||
this.RootRow.Rows.ClearIndex("_ID");
|
||||
var index = UpdateAllSampleFields(this.RootRow, samples, 0, "");
|
||||
this.SampleDigest = sample_digest;
|
||||
|
||||
// Clear out any left-over rows
|
||||
for (var i = index; i < this.RootRow.Rows.Rows.length; i++)
|
||||
{
|
||||
var row = this.RootRow.Rows.Rows[i];
|
||||
DOM.Node.Hide(row.Node);
|
||||
}
|
||||
}
|
||||
|
||||
else if (this.Visible)
|
||||
{
|
||||
// Otherwise just update the existing sample fields
|
||||
UpdateChangedSampleFields(this.RootRow, samples, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function GrowGrid(parent_row, nb_samples)
|
||||
{
|
||||
parent_row.Rows.Clear();
|
||||
|
||||
for (var i = 0; i < nb_samples; i++)
|
||||
{
|
||||
var cell_data =
|
||||
{
|
||||
_ID: i,
|
||||
Name: "",
|
||||
Length: "",
|
||||
Self: "",
|
||||
Calls: "",
|
||||
Recurse: "",
|
||||
};
|
||||
|
||||
var cell_classes =
|
||||
{
|
||||
Name: "SampleNameCell",
|
||||
Length: "SampleTimeCell",
|
||||
Self: "SampleTimeCell",
|
||||
Calls: "SampleCountCell",
|
||||
Recurse: "SampleCountCell",
|
||||
};
|
||||
|
||||
parent_row.Rows.Add(cell_data, null, cell_classes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function UpdateAllSampleFields(parent_row, samples, index, indent)
|
||||
{
|
||||
for (var i in samples)
|
||||
{
|
||||
var sample = samples[i];
|
||||
|
||||
// Match row allocation in GrowGrid
|
||||
var row = parent_row.Rows.Rows[index++];
|
||||
|
||||
// Sample row may have been hidden previously
|
||||
DOM.Node.Show(row.Node);
|
||||
|
||||
// Assign unique ID so that the common fast path of updating sample times only
|
||||
// can lookup target samples in the grid
|
||||
row.CellData._ID = sample.id;
|
||||
parent_row.Rows.AddRowToIndex("_ID", sample.id, row);
|
||||
|
||||
// Record sample name for later comparison
|
||||
row.CellData.Name = sample.name.string;
|
||||
|
||||
// Set sample name and colour
|
||||
var name_node = row.CellNodes["Name"];
|
||||
name_node.innerHTML = indent + sample.name.string;
|
||||
DOM.Node.SetColour(name_node, sample.colour);
|
||||
|
||||
row.CellNodes["Length"].innerHTML = sample.ms_length;
|
||||
row.CellNodes["Self"].innerHTML = sample.ms_self;
|
||||
row.CellNodes["Calls"].innerHTML = sample.call_count;
|
||||
row.CellNodes["Recurse"].innerHTML = sample.recurse_depth;
|
||||
|
||||
index = UpdateAllSampleFields(parent_row, sample.children, index, indent + " ");
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
function UpdateChangedSampleFields(parent_row, samples, indent)
|
||||
{
|
||||
for (var i in samples)
|
||||
{
|
||||
var sample = samples[i];
|
||||
|
||||
var row = parent_row.Rows.GetBy("_ID", sample.id);
|
||||
if (row)
|
||||
{
|
||||
row.CellNodes["Length"].innerHTML = sample.ms_length;
|
||||
row.CellNodes["Self"].innerHTML = sample.ms_self;
|
||||
row.CellNodes["Calls"].innerHTML = sample.call_count;
|
||||
row.CellNodes["Recurse"].innerHTML = sample.recurse_depth;
|
||||
|
||||
// Sample name will change when it switches from hash ID to network-retrieved
|
||||
// name. Quickly check that before re-applying the HTML for the name.
|
||||
if (row.CellData.Name != sample.name.string)
|
||||
{
|
||||
var name_node = row.CellNodes["Name"];
|
||||
row.CellData.Name = sample.name.string;
|
||||
name_node.innerHTML = indent + sample.name.string;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateChangedSampleFields(parent_row, sample.children, indent + " ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return SampleWindow;
|
||||
})();
|
|
@ -1,28 +0,0 @@
|
|||
|
||||
|
||||
ThreadFrame = (function()
|
||||
{
|
||||
function ThreadFrame(message)
|
||||
{
|
||||
// Persist the required message data
|
||||
this.NbSamples = message.nb_samples;
|
||||
this.SampleDigest = message.sample_digest;
|
||||
this.Samples = message.samples;
|
||||
|
||||
// Calculate the frame start/end times
|
||||
this.StartTime_us = 0;
|
||||
this.EndTime_us = 0;
|
||||
var nb_root_samples = this.Samples.length;
|
||||
if (nb_root_samples > 0)
|
||||
{
|
||||
var last_sample = this.Samples[nb_root_samples - 1];
|
||||
this.StartTime_us = this.Samples[0].us_start;
|
||||
this.EndTime_us = last_sample.us_start + last_sample.us_length;
|
||||
}
|
||||
|
||||
this.Length_us = this.EndTime_us - this.StartTime_us;
|
||||
}
|
||||
|
||||
|
||||
return ThreadFrame;
|
||||
})();
|
|
@ -1,379 +0,0 @@
|
|||
|
||||
|
||||
TimelineRow = (function()
|
||||
{
|
||||
var row_template = function(){/*
|
||||
<div class='TimelineRow'>
|
||||
<div class='TimelineRowCheck TimelineBox'>
|
||||
<input class='TimelineRowCheckbox' type='checkbox' />
|
||||
</div>
|
||||
<div class='TimelineRowExpand TimelineBox NoSelect'>
|
||||
<div class='TimelineRowExpandButton'>+</div>
|
||||
</div>
|
||||
<div class='TimelineRowExpand TimelineBox NoSelect'>
|
||||
<div class='TimelineRowExpandButton'>-</div>
|
||||
</div>
|
||||
<div class='TimelineRowLabel TimelineBox'></div>
|
||||
<canvas class='TimelineRowCanvas'></canvas>
|
||||
<div style="clear:left"></div>
|
||||
</div>
|
||||
*/}.toString().split(/\n/).slice(1, -1).join("\n");
|
||||
|
||||
|
||||
var CANVAS_Y_OFFSET = 0;
|
||||
var CANVAS_BORDER = 1;
|
||||
var SAMPLE_HEIGHT = 16;
|
||||
var SAMPLE_BORDER = 1;
|
||||
var SAMPLE_Y_SPACING = SAMPLE_HEIGHT + SAMPLE_BORDER * 2;
|
||||
var SAMPLE_Y_OFFSET = CANVAS_Y_OFFSET + CANVAS_BORDER + 1;
|
||||
|
||||
|
||||
function TimelineRow(name, width, parent_node, frame_history, check_handler)
|
||||
{
|
||||
this.Name = name;
|
||||
|
||||
// Create the row HTML and add to the parent
|
||||
this.ContainerNode = DOM.Node.CreateHTML(row_template);
|
||||
this.Node = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowData");
|
||||
this.LabelNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowLabel");
|
||||
this.LabelNode.innerHTML = name;
|
||||
this.CheckboxNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCheckbox");
|
||||
var expand_node_0 = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowExpand", 0);
|
||||
var expand_node_1 = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowExpand", 1);
|
||||
this.IncNode = DOM.Node.FindWithClass(expand_node_0, "TimelineRowExpandButton");
|
||||
this.DecNode = DOM.Node.FindWithClass(expand_node_1, "TimelineRowExpandButton");
|
||||
this.CanvasNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCanvas");
|
||||
parent_node.appendChild(this.ContainerNode);
|
||||
|
||||
// All sample view windows visible by default
|
||||
this.CheckboxNode.checked = true;
|
||||
DOM.Event.AddHandler(this.CheckboxNode, "change", function(evt) { check_handler(name, evt); });
|
||||
|
||||
// Manually hook-up events to simulate div:active
|
||||
// I can't get the equivalent CSS to work in Firefox, so...
|
||||
DOM.Event.AddHandler(this.IncNode, "mousedown", ExpandButtonDown);
|
||||
DOM.Event.AddHandler(this.IncNode, "mouseup", ExpandButtonUp);
|
||||
DOM.Event.AddHandler(this.IncNode, "mouseleave", ExpandButtonUp);
|
||||
DOM.Event.AddHandler(this.DecNode, "mousedown", ExpandButtonDown);
|
||||
DOM.Event.AddHandler(this.DecNode, "mouseup", ExpandButtonUp);
|
||||
DOM.Event.AddHandler(this.DecNode, "mouseleave", ExpandButtonUp);
|
||||
|
||||
// Pressing +/i increases/decreases depth
|
||||
DOM.Event.AddHandler(this.IncNode, "click", Bind(IncDepth, this));
|
||||
DOM.Event.AddHandler(this.DecNode, "click", Bind(DecDepth, this));
|
||||
|
||||
// Setup the canvas
|
||||
this.Depth = 1;
|
||||
this.Ctx = this.CanvasNode.getContext("2d");
|
||||
this.SetSize(width);
|
||||
this.Clear();
|
||||
|
||||
// Frame index to start at when looking for first visible sample
|
||||
this.StartFrameIndex = 0;
|
||||
|
||||
this.FrameHistory = frame_history;
|
||||
this.VisibleFrames = [ ];
|
||||
this.VisibleTimeRange = null;
|
||||
|
||||
// Sample the mouse is currently hovering over
|
||||
this.HoverSample = null;
|
||||
this.HoverSampleDepth = 0;
|
||||
|
||||
// Currently selected sample
|
||||
this.SelectedSample = null;
|
||||
this.SelectedSampleDepth = 0;
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.SetSize = function(width)
|
||||
{
|
||||
// Must ALWAYS set the width/height properties together. Setting one on its own has weird side-effects.
|
||||
this.CanvasNode.width = width;
|
||||
this.CanvasNode.height = CANVAS_BORDER + SAMPLE_BORDER + SAMPLE_Y_SPACING * this.Depth;
|
||||
this.Draw(true);
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.Clear = function()
|
||||
{
|
||||
// Fill box that shows the boundary between thread rows
|
||||
this.Ctx.fillStyle = "#666"
|
||||
var b = CANVAS_BORDER;
|
||||
this.Ctx.fillRect(b, b, this.CanvasNode.width - b * 2, this.CanvasNode.height - b * 2);
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.SetVisibleFrames = function(time_range)
|
||||
{
|
||||
// Clear previous visible list
|
||||
this.VisibleFrames = [ ];
|
||||
if (this.FrameHistory.length == 0)
|
||||
return;
|
||||
|
||||
// Store a copy of the visible time range rather than referencing it
|
||||
// This prevents external modifications to the time range from affecting rendering/selection
|
||||
time_range = time_range.Clone();
|
||||
this.VisibleTimeRange = time_range;
|
||||
|
||||
// The frame history can be reset outside this class
|
||||
// This also catches the overflow to the end of the frame list below when a thread stops sending samples
|
||||
var max_frame = Math.max(this.FrameHistory.length - 1, 0);
|
||||
var start_frame_index = Math.min(this.StartFrameIndex, max_frame);
|
||||
|
||||
// First do a back-track in case the time range moves negatively
|
||||
while (start_frame_index > 0)
|
||||
{
|
||||
var frame = this.FrameHistory[start_frame_index];
|
||||
if (time_range.Start_us > frame.StartTime_us)
|
||||
break;
|
||||
start_frame_index--;
|
||||
}
|
||||
|
||||
// Then search from this point for the first visible frame
|
||||
while (start_frame_index < this.FrameHistory.length)
|
||||
{
|
||||
var frame = this.FrameHistory[start_frame_index];
|
||||
if (frame.EndTime_us > time_range.Start_us)
|
||||
break;
|
||||
start_frame_index++;
|
||||
}
|
||||
|
||||
// Gather all frames up to the end point
|
||||
this.StartFrameIndex = start_frame_index;
|
||||
for (var i = start_frame_index; i < this.FrameHistory.length; i++)
|
||||
{
|
||||
var frame = this.FrameHistory[i];
|
||||
if (frame.StartTime_us > time_range.End_us)
|
||||
break;
|
||||
this.VisibleFrames.push(frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.Draw = function(draw_text)
|
||||
{
|
||||
this.Clear();
|
||||
|
||||
// Draw all root samples in the visible frame set
|
||||
for (var i in this.VisibleFrames)
|
||||
{
|
||||
var frame = this.VisibleFrames[i];
|
||||
DrawSamples(this, frame.Samples, 1, draw_text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function DrawSamples(self, samples, depth, draw_text)
|
||||
{
|
||||
for (var i in samples)
|
||||
{
|
||||
var sample = samples[i];
|
||||
DrawSample(self, sample, depth, draw_text);
|
||||
|
||||
if (depth < self.Depth && sample.children != null)
|
||||
DrawSamples(self, sample.children, depth + 1, draw_text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.UpdateHoverSample = function(mouse_state, x_offset)
|
||||
{
|
||||
var hover = GetSampleAtPosition(this, mouse_state, x_offset);
|
||||
if (hover)
|
||||
this.SetHoverSample(hover[1], hover[2]);
|
||||
return hover;
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.UpdateSelectedSample = function(mouse_state, x_offset)
|
||||
{
|
||||
var select = GetSampleAtPosition(this, mouse_state, x_offset);
|
||||
if (select)
|
||||
this.SetSelectedSample(select[1], select[2]);
|
||||
return select;
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.SetHoverSample = function(sample, sample_depth)
|
||||
{
|
||||
if (sample != this.HoverSample)
|
||||
{
|
||||
// Discard old highlight
|
||||
// TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate
|
||||
var old_sample = this.HoverSample;
|
||||
var old_sample_depth = this.HoverSampleDepth;
|
||||
this.HoverSample = null;
|
||||
this.HoverSampleDepth = 0;
|
||||
DrawSample(this, old_sample, old_sample_depth, true);
|
||||
|
||||
// Add new highlight
|
||||
this.HoverSample = sample;
|
||||
this.HoverSampleDepth = sample_depth;
|
||||
DrawSample(this, sample, sample_depth, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.SetSelectedSample = function(sample, sample_depth)
|
||||
{
|
||||
if (sample != this.SelectedSample)
|
||||
{
|
||||
// Discard old highlight
|
||||
// TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate
|
||||
var old_sample = this.SelectedSample;
|
||||
var old_sample_depth = this.SelectedSampleDepth;
|
||||
this.SelectedSample = null;
|
||||
this.SelectedSampleDepth = 0;
|
||||
DrawSample(this, old_sample, old_sample_depth, true);
|
||||
|
||||
// Add new highlight
|
||||
this.SelectedSample = sample;
|
||||
this.SelectedSampleDepth = sample_depth;
|
||||
DrawSample(this, sample, sample_depth, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ExpandButtonDown(evt)
|
||||
{
|
||||
var node = DOM.Event.GetNode(evt);
|
||||
DOM.Node.AddClass(node, "TimelineRowExpandButtonActive");
|
||||
}
|
||||
|
||||
|
||||
function ExpandButtonUp(evt)
|
||||
{
|
||||
var node = DOM.Event.GetNode(evt);
|
||||
DOM.Node.RemoveClass(node, "TimelineRowExpandButtonActive");
|
||||
}
|
||||
|
||||
|
||||
function IncDepth(self)
|
||||
{
|
||||
self.Depth++;
|
||||
self.SetSize(self.CanvasNode.width);
|
||||
}
|
||||
|
||||
|
||||
function DecDepth(self)
|
||||
{
|
||||
if (self.Depth > 1)
|
||||
{
|
||||
self.Depth--;
|
||||
self.SetSize(self.CanvasNode.width);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function GetSampleAtPosition(self, mouse_state, x_offset)
|
||||
{
|
||||
// Mouse movement can occur before any data is sent to a timeline row
|
||||
var time_range = self.VisibleTimeRange;
|
||||
if (time_range == null)
|
||||
return;
|
||||
|
||||
// Get the time the mouse is over
|
||||
var x = mouse_state.Position[0] - x_offset;
|
||||
var time_us = time_range.Start_us + x / time_range.usPerPixel;
|
||||
|
||||
var canvas_y_offset = DOM.Node.GetPosition(self.CanvasNode)[1];
|
||||
var mouse_y_offset = mouse_state.Position[1] - canvas_y_offset;
|
||||
mouse_y_offset = Math.min(Math.max(mouse_y_offset, 0), self.CanvasNode.height);
|
||||
var depth = Math.floor(mouse_y_offset / SAMPLE_Y_SPACING) + 1;
|
||||
|
||||
// Search for the first frame to intersect this time
|
||||
for (var i in self.VisibleFrames)
|
||||
{
|
||||
var frame = self.VisibleFrames[i];
|
||||
if (time_us >= frame.StartTime_us && time_us < frame.EndTime_us)
|
||||
{
|
||||
var found_sample = FindSample(self, frame.Samples, time_us, depth, 1);
|
||||
if (found_sample != null)
|
||||
return [ frame, found_sample[0], found_sample[1] ];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function FindSample(self, samples, time_us, target_depth, depth)
|
||||
{
|
||||
for (var i in samples)
|
||||
{
|
||||
var sample = samples[i];
|
||||
if (depth == target_depth)
|
||||
{
|
||||
if (time_us >= sample.us_start && time_us < sample.us_start + sample.us_length)
|
||||
return [ sample, depth ];
|
||||
}
|
||||
|
||||
else if (depth < target_depth && sample.children != null)
|
||||
{
|
||||
var found_sample = FindSample(self, sample.children, time_us, target_depth, depth + 1);
|
||||
if (found_sample != null)
|
||||
return found_sample;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function DrawSample(self, sample, depth, draw_text)
|
||||
{
|
||||
if (sample == null)
|
||||
return;
|
||||
|
||||
// Determine pixel range of the sample
|
||||
var time_range = self.VisibleTimeRange;
|
||||
var x0 = time_range.PixelOffset(sample.us_start);
|
||||
var x1 = x0 + time_range.PixelSize(sample.us_length);
|
||||
|
||||
// Clip to padded timeline row
|
||||
var min_x = 3;
|
||||
var max_x = self.CanvasNode.width - 5;
|
||||
x0 = Math.min(Math.max(x0, min_x), max_x);
|
||||
x1 = Math.min(Math.max(x1, min_x), max_x);
|
||||
|
||||
var offset_x = x0;
|
||||
var offset_y = SAMPLE_Y_OFFSET + (depth - 1) * SAMPLE_Y_SPACING;
|
||||
var size_x = x1 - x0;
|
||||
var size_y = SAMPLE_HEIGHT;
|
||||
|
||||
// Normal rendering
|
||||
var ctx = self.Ctx;
|
||||
ctx.fillStyle = sample.colour;
|
||||
ctx.fillRect(offset_x, offset_y, size_x, size_y);
|
||||
|
||||
// Highlight rendering
|
||||
var b = (sample == self.HoverSample) ? 255 : 0;
|
||||
var r = (sample == self.SelectedSample) ? 255 : 0;
|
||||
if (b + r > 0)
|
||||
{
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = "rgb(" + r + ", 0, " + b + ")";
|
||||
ctx.strokeRect(offset_x + 0.5, offset_y + 0.5, size_x - 1, size_y - 1);
|
||||
}
|
||||
|
||||
// Draw sample names clipped to the bounds of the sample
|
||||
// Also reject tiny samples with no space to render text
|
||||
if (draw_text && size_x > 8)
|
||||
{
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(offset_x + 2.5, offset_y + 1.5, size_x - 5, size_y - 3);
|
||||
ctx.clip();
|
||||
ctx.font = "9px verdana";
|
||||
ctx.fillStyle = "black";
|
||||
var text = sample.name.string
|
||||
text += " (" + sample.ms_length + "ms";
|
||||
text += ", " + sample.call_count + "c)";
|
||||
ctx.fillText(text, offset_x + 5.5, offset_y + 1.5 + 9);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return TimelineRow;
|
||||
})();
|
|
@ -1,284 +0,0 @@
|
|||
|
||||
//
|
||||
// TODO: Use WebGL and instancing for quicker renders
|
||||
//
|
||||
|
||||
|
||||
TimelineWindow = (function()
|
||||
{
|
||||
var BORDER = 10;
|
||||
|
||||
var ROW_START_SIZE = 210;
|
||||
|
||||
var ROW_END_SIZE = 20; // make room for scrollbar
|
||||
|
||||
var box_template = "<div class='TimelineBox'></div>";
|
||||
|
||||
function TimelineWindow(wm, settings, server, check_handler)
|
||||
{
|
||||
this.Settings = settings;
|
||||
|
||||
// Ordered list of thread rows on the timeline
|
||||
this.ThreadRows = [ ];
|
||||
|
||||
// Create window and containers
|
||||
this.Window = wm.AddWindow("Timeline", 10, 20, 100, 100);
|
||||
this.Window.ShowNoAnim();
|
||||
this.TimelineContainer = this.Window.AddControlNew(new WM.Container(10, 10, 800, 160));
|
||||
DOM.Node.AddClass(this.TimelineContainer.Node, "TimelineContainer");
|
||||
|
||||
var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel";
|
||||
DOM.Event.AddHandler(this.TimelineContainer.Node, mouse_wheel_event, Bind(OnMouseScroll, this));
|
||||
|
||||
// Setup timeline manipulation
|
||||
this.MouseDown = false;
|
||||
this.LastMouseState = null;
|
||||
this.TimelineMoved = false;
|
||||
this.OnHoverHandler = null;
|
||||
this.OnSelectedHandler = null;
|
||||
DOM.Event.AddHandler(this.TimelineContainer.Node, "mousedown", Bind(OnMouseDown, this));
|
||||
DOM.Event.AddHandler(this.TimelineContainer.Node, "mouseup", Bind(OnMouseUp, this));
|
||||
DOM.Event.AddHandler(this.TimelineContainer.Node, "mousemove", Bind(OnMouseMove, this));
|
||||
|
||||
// Set time range AFTER the window has been created, as it uses the window to determine pixel coverage
|
||||
this.TimeRange = new PixelTimeRange(0, 200 * 1000, RowWidth(this));
|
||||
|
||||
this.CheckHandler = check_handler;
|
||||
|
||||
this.Window.SetOnResize(Bind(OnUserResize, this));
|
||||
}
|
||||
|
||||
|
||||
TimelineWindow.prototype.SetOnHover = function(handler)
|
||||
{
|
||||
this.OnHoverHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
TimelineWindow.prototype.SetOnSelected = function(handler)
|
||||
{
|
||||
this.OnSelectedHandler = handler;
|
||||
}
|
||||
|
||||
TimelineWindow.prototype.WindowResized = function(width, height, top_window)
|
||||
{
|
||||
// Resize window
|
||||
var top = top_window.Position[1] + top_window.Size[1] + 10;
|
||||
this.Window.SetPosition(10, top);
|
||||
this.Window.SetSize(width - 2 * 10, 260);
|
||||
|
||||
ResizeInternals(this);
|
||||
}
|
||||
|
||||
|
||||
TimelineWindow.prototype.ResetTimeRange = function()
|
||||
{
|
||||
this.TimeRange.SetStart(0);
|
||||
}
|
||||
|
||||
|
||||
TimelineWindow.prototype.OnSamples = function(thread_name, frame_history)
|
||||
{
|
||||
// Shift the timeline to the last entry on this thread
|
||||
// As multiple threads come through here with different end frames, only do this for the latest
|
||||
var last_frame = frame_history[frame_history.length - 1];
|
||||
if (last_frame.EndTime_us > this.TimeRange.End_us)
|
||||
this.TimeRange.SetEnd(last_frame.EndTime_us);
|
||||
|
||||
// Search for the index of this thread
|
||||
var thread_index = -1;
|
||||
for (var i in this.ThreadRows)
|
||||
{
|
||||
if (this.ThreadRows[i].Name == thread_name)
|
||||
{
|
||||
thread_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If this thread has not been seen before, add a new row to the list and re-sort
|
||||
if (thread_index == -1)
|
||||
{
|
||||
var row = new TimelineRow(thread_name, RowWidth(this), this.TimelineContainer.Node, frame_history, this.CheckHandler);
|
||||
this.ThreadRows.push(row);
|
||||
this.ThreadRows.sort(function(a, b) { return b.Name.localeCompare(a.Name); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TimelineWindow.prototype.DrawAllRows = function()
|
||||
{
|
||||
var time_range = this.TimeRange;
|
||||
var draw_text = this.Settings.IsPaused;
|
||||
for (var i in this.ThreadRows)
|
||||
{
|
||||
var thread_row = this.ThreadRows[i];
|
||||
thread_row.SetVisibleFrames(time_range);
|
||||
thread_row.Draw(draw_text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function RowXOffset(self)
|
||||
{
|
||||
// Add sizing of the label
|
||||
// TODO: Use computed size
|
||||
return DOM.Node.GetPosition(self.TimelineContainer.Node)[0] + ROW_START_SIZE;
|
||||
}
|
||||
|
||||
|
||||
function RowWidth(self)
|
||||
{
|
||||
// Subtract sizing of the label
|
||||
// TODO: Use computed size
|
||||
return self.TimelineContainer.Size[0] - (ROW_START_SIZE + ROW_END_SIZE);
|
||||
}
|
||||
|
||||
function OnUserResize(self, evt)
|
||||
{
|
||||
ResizeInternals(self);
|
||||
}
|
||||
|
||||
function ResizeInternals(self)
|
||||
{
|
||||
// Resize controls
|
||||
var parent_size = self.Window.Size;
|
||||
self.TimelineContainer.SetPosition(BORDER, 10);
|
||||
self.TimelineContainer.SetSize(parent_size[0] - 2 * BORDER, parent_size[1] - 40);
|
||||
|
||||
// Resize rows
|
||||
var row_width = RowWidth(self);
|
||||
for (var i in self.ThreadRows)
|
||||
{
|
||||
var row = self.ThreadRows[i];
|
||||
row.SetSize(row_width);
|
||||
}
|
||||
|
||||
// Adjust time range to new width
|
||||
self.TimeRange.SetPixelSpan(row_width);
|
||||
self.DrawAllRows();
|
||||
}
|
||||
|
||||
|
||||
function OnMouseScroll(self, evt)
|
||||
{
|
||||
var mouse_state = new Mouse.State(evt);
|
||||
var scale = 1.11;
|
||||
if (mouse_state.WheelDelta > 0)
|
||||
scale = 1 / scale;
|
||||
|
||||
// What time is the mouse hovering over?
|
||||
var x = mouse_state.Position[0] - RowXOffset(self);
|
||||
var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel;
|
||||
|
||||
// Calculate start time relative to the mouse hover position
|
||||
var time_start_us = self.TimeRange.Start_us - time_us;
|
||||
|
||||
// Scale and offset back to the hover time
|
||||
self.TimeRange.Set(time_start_us * scale + time_us, self.TimeRange.Span_us * scale);
|
||||
self.DrawAllRows();
|
||||
|
||||
// Prevent vertical scrolling on mouse-wheel
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
|
||||
|
||||
function OnMouseDown(self, evt)
|
||||
{
|
||||
// Only manipulate the timelime when paused
|
||||
if (!self.Settings.IsPaused)
|
||||
return;
|
||||
|
||||
self.MouseDown = true;
|
||||
self.LastMouseState = new Mouse.State(evt);
|
||||
self.TimelineMoved = false;
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
|
||||
|
||||
function OnMouseUp(self, evt)
|
||||
{
|
||||
// Only manipulate the timelime when paused
|
||||
if (!self.Settings.IsPaused)
|
||||
return;
|
||||
|
||||
var mouse_state = new Mouse.State(evt);
|
||||
|
||||
self.MouseDown = false;
|
||||
|
||||
if (!self.TimelineMoved)
|
||||
{
|
||||
// Search for the row being clicked and update its selection
|
||||
var row_node = DOM.Event.GetNode(evt);
|
||||
for (var i in self.ThreadRows)
|
||||
{
|
||||
var thread_row = self.ThreadRows[i];
|
||||
if (thread_row.CanvasNode == row_node)
|
||||
{
|
||||
var select = thread_row.UpdateSelectedSample(mouse_state, RowXOffset(self));
|
||||
|
||||
// Call any selection handlers
|
||||
if (self.OnSelectedHandler)
|
||||
self.OnSelectedHandler(thread_row.Name, select);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnMouseMove(self, evt)
|
||||
{
|
||||
// Only manipulate the timelime when paused
|
||||
if (!self.Settings.IsPaused)
|
||||
return;
|
||||
|
||||
var mouse_state = new Mouse.State(evt);
|
||||
|
||||
if (self.MouseDown)
|
||||
{
|
||||
// Get the time the mouse is over
|
||||
var x = mouse_state.Position[0] - RowXOffset(self);
|
||||
var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel;
|
||||
|
||||
// Shift the visible time range with mouse movement
|
||||
var time_offset_us = (mouse_state.Position[0] - self.LastMouseState.Position[0]) / self.TimeRange.usPerPixel;
|
||||
if (time_offset_us)
|
||||
{
|
||||
self.TimeRange.SetStart(self.TimeRange.Start_us - time_offset_us);
|
||||
self.DrawAllRows();
|
||||
self.TimelineMoved = true;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Highlight any samples the mouse moves over
|
||||
var row_node = DOM.Event.GetNode(evt);
|
||||
for (var i in self.ThreadRows)
|
||||
{
|
||||
var thread_row = self.ThreadRows[i];
|
||||
if (thread_row.CanvasNode == row_node)
|
||||
{
|
||||
var hover = thread_row.UpdateHoverSample(mouse_state, RowXOffset(self));
|
||||
|
||||
if (self.OnHoverHandler)
|
||||
self.OnHoverHandler(thread_row.Name, hover);
|
||||
}
|
||||
else
|
||||
{
|
||||
thread_row.SetHoverSample(null, 0);
|
||||
if (self.OnHoverHandler)
|
||||
self.OnHoverHandler(thread_row.Name, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.LastMouseState = mouse_state;
|
||||
}
|
||||
|
||||
|
||||
return TimelineWindow;
|
||||
})();
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
|
||||
TitleWindow = (function()
|
||||
{
|
||||
function TitleWindow(wm, settings, server, connection_address)
|
||||
{
|
||||
this.Settings = settings;
|
||||
|
||||
this.Window = wm.AddWindow(" Remotery", 10, 10, 100, 100);
|
||||
this.Window.ShowNoAnim();
|
||||
|
||||
this.PingContainer = this.Window.AddControlNew(new WM.Container(4, -13, 10, 10));
|
||||
DOM.Node.AddClass(this.PingContainer.Node, "PingContainer");
|
||||
|
||||
this.EditBox = this.Window.AddControlNew(new WM.EditBox(10, 5, 300, 18, "Connection Address", connection_address));
|
||||
|
||||
// Setup pause button
|
||||
this.PauseButton = this.Window.AddControlNew(new WM.Button("Pause", 5, 5, { toggle: true }));
|
||||
this.PauseButton.SetOnClick(Bind(OnPausePressed, this));
|
||||
|
||||
server.AddMessageHandler("PING", Bind(OnPing, this));
|
||||
|
||||
this.Window.SetOnResize(Bind(OnUserResize, this));
|
||||
}
|
||||
|
||||
|
||||
TitleWindow.prototype.SetConnectionAddressChanged = function(handler)
|
||||
{
|
||||
this.EditBox.SetChangeHandler(handler);
|
||||
}
|
||||
|
||||
|
||||
TitleWindow.prototype.WindowResized = function(width, height)
|
||||
{
|
||||
this.Window.SetSize(width - 2 * 10, 50);
|
||||
ResizeInternals(this);
|
||||
}
|
||||
|
||||
function OnUserResize(self, evt)
|
||||
{
|
||||
ResizeInternals(self);
|
||||
}
|
||||
|
||||
function ResizeInternals(self)
|
||||
{
|
||||
self.PauseButton.SetPosition(self.Window.Size[0] - 60, 5);
|
||||
}
|
||||
|
||||
|
||||
function OnPausePressed(self)
|
||||
{
|
||||
self.Settings.IsPaused = self.PauseButton.IsPressed();
|
||||
if (self.Settings.IsPaused)
|
||||
self.PauseButton.SetText("Paused");
|
||||
else
|
||||
self.PauseButton.SetText("Pause");
|
||||
}
|
||||
|
||||
|
||||
function OnPing(self, server)
|
||||
{
|
||||
// Set the ping container as active and take it off half a second later
|
||||
DOM.Node.AddClass(self.PingContainer.Node, "PingContainerActive");
|
||||
window.setTimeout(Bind(function(self)
|
||||
{
|
||||
DOM.Node.RemoveClass(self.PingContainer.Node, "PingContainerActive");
|
||||
}, self), 500);
|
||||
}
|
||||
|
||||
|
||||
return TitleWindow;
|
||||
})();
|
|
@ -1,137 +0,0 @@
|
|||
|
||||
WebSocketConnection = (function()
|
||||
{
|
||||
function WebSocketConnection()
|
||||
{
|
||||
this.MessageHandlers = { };
|
||||
this.Socket = null;
|
||||
this.Console = null;
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.SetConsole = function(console)
|
||||
{
|
||||
this.Console = console;
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.Connected = function()
|
||||
{
|
||||
// Will return true if the socket is also in the process of connecting
|
||||
return this.Socket != null;
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.AddConnectHandler = function(handler)
|
||||
{
|
||||
this.AddMessageHandler("__OnConnect__", handler);
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.AddDisconnectHandler = function(handler)
|
||||
{
|
||||
this.AddMessageHandler("__OnDisconnect__", handler);
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.AddMessageHandler = function(message_name, handler)
|
||||
{
|
||||
// Create the message handler array on-demand
|
||||
if (!(message_name in this.MessageHandlers))
|
||||
this.MessageHandlers[message_name] = [ ];
|
||||
this.MessageHandlers[message_name].push(handler);
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.Connect = function(address)
|
||||
{
|
||||
// Disconnect if already connected
|
||||
if (this.Connected())
|
||||
this.Disconnect();
|
||||
|
||||
Log(this, "Connecting to " + address);
|
||||
|
||||
this.Socket = new WebSocket(address);
|
||||
this.Socket.binaryType = "arraybuffer";
|
||||
this.Socket.onopen = Bind(OnOpen, this);
|
||||
this.Socket.onmessage = Bind(OnMessage, this);
|
||||
this.Socket.onclose = Bind(OnClose, this);
|
||||
this.Socket.onerror = Bind(OnError, this);
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.Disconnect = function()
|
||||
{
|
||||
Log(this, "Disconnecting");
|
||||
if (this.Connected())
|
||||
this.Socket.close();
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.Send = function(msg)
|
||||
{
|
||||
if (this.Connected())
|
||||
this.Socket.send(msg);
|
||||
}
|
||||
|
||||
|
||||
function Log(self, message)
|
||||
{
|
||||
self.Console.Log(message);
|
||||
}
|
||||
|
||||
|
||||
function CallMessageHandlers(self, message_name, data_view)
|
||||
{
|
||||
if (message_name in self.MessageHandlers)
|
||||
{
|
||||
var handlers = self.MessageHandlers[message_name];
|
||||
for (var i in handlers)
|
||||
handlers[i](self, data_view);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnOpen(self, event)
|
||||
{
|
||||
Log(self, "Connected");
|
||||
CallMessageHandlers(self, "__OnConnect__");
|
||||
}
|
||||
|
||||
|
||||
function OnClose(self, event)
|
||||
{
|
||||
// Clear all references
|
||||
self.Socket.onopen = null;
|
||||
self.Socket.onmessage = null;
|
||||
self.Socket.onclose = null;
|
||||
self.Socket.onerror = null;
|
||||
self.Socket = null;
|
||||
|
||||
Log(self, "Disconnected");
|
||||
CallMessageHandlers(self, "__OnDisconnect__");
|
||||
}
|
||||
|
||||
|
||||
function OnError(self, event)
|
||||
{
|
||||
Log(self, "Connection Error ");
|
||||
}
|
||||
|
||||
|
||||
function OnMessage(self, event)
|
||||
{
|
||||
var data_view = new DataView(event.data);
|
||||
|
||||
var id = String.fromCharCode(
|
||||
data_view.getInt8(0),
|
||||
data_view.getInt8(1),
|
||||
data_view.getInt8(2),
|
||||
data_view.getInt8(3));
|
||||
|
||||
CallMessageHandlers(self, id, data_view);
|
||||
}
|
||||
|
||||
|
||||
return WebSocketConnection;
|
||||
})();
|
|
@ -1,234 +0,0 @@
|
|||
|
||||
body
|
||||
{
|
||||
/* Take up the full page */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
|
||||
background-color: #999;
|
||||
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
|
||||
/* Override default container style to remove 3D effect */
|
||||
.Container
|
||||
{
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
/* Override default edit box style to remove 3D effect */
|
||||
.EditBox
|
||||
{
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
width:200;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ConsoleText
|
||||
{
|
||||
overflow:auto;
|
||||
color: #BBB;
|
||||
font: 9px Verdana;
|
||||
margin: 2px;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
|
||||
.PingContainer
|
||||
{
|
||||
background-color: #F55;
|
||||
border-radius: 2px;
|
||||
|
||||
/* Transition from green is gradual */
|
||||
transition: background-color 0.25s ease-in;
|
||||
}
|
||||
|
||||
|
||||
.PingContainerActive
|
||||
{
|
||||
background-color: #5F5;
|
||||
|
||||
/* Transition to green is instant */
|
||||
transition: none;
|
||||
}
|
||||
|
||||
|
||||
.SampleNameCell
|
||||
{
|
||||
width:243px;
|
||||
}
|
||||
.SampleTimeCell
|
||||
{
|
||||
width:52px;
|
||||
}
|
||||
.SampleCountCell
|
||||
{
|
||||
width:43px;
|
||||
}
|
||||
.SampleTitleNameCell
|
||||
{
|
||||
width:238px;
|
||||
|
||||
padding: 1px 1px 1px 2px;
|
||||
border: 1px solid;
|
||||
border-radius: 2px;
|
||||
|
||||
border-top-color:#555;
|
||||
border-left-color:#555;
|
||||
border-bottom-color:#111;
|
||||
border-right-color:#111;
|
||||
|
||||
background: #222;
|
||||
}
|
||||
.SampleTitleTimeCell
|
||||
{
|
||||
width:47px;
|
||||
|
||||
padding: 1px 1px 1px 2px;
|
||||
border: 1px solid;
|
||||
border-radius: 2px;
|
||||
|
||||
border-top-color:#555;
|
||||
border-left-color:#555;
|
||||
border-bottom-color:#111;
|
||||
border-right-color:#111;
|
||||
|
||||
background: #222;
|
||||
}
|
||||
.SampleTitleCountCell
|
||||
{
|
||||
width:38px;
|
||||
|
||||
padding: 1px 1px 1px 2px;
|
||||
border: 1px solid;
|
||||
border-radius: 2px;
|
||||
|
||||
border-top-color:#555;
|
||||
border-left-color:#555;
|
||||
border-bottom-color:#111;
|
||||
border-right-color:#111;
|
||||
|
||||
background: #222;
|
||||
}
|
||||
|
||||
|
||||
.TimelineBox
|
||||
{
|
||||
/* Following style generally copies GridRowCell.GridGroup from BrowserLib */
|
||||
|
||||
padding: 1px 1px 1px 2px;
|
||||
margin: 1px;
|
||||
|
||||
border: 1px solid;
|
||||
border-radius: 2px;
|
||||
border-top-color:#555;
|
||||
border-left-color:#555;
|
||||
border-bottom-color:#111;
|
||||
border-right-color:#111;
|
||||
|
||||
background: #222;
|
||||
|
||||
font: 9px Verdana;
|
||||
color: #BBB;
|
||||
}
|
||||
.TimelineRow
|
||||
{
|
||||
width: 100%;
|
||||
}
|
||||
.TimelineRowCheckbox
|
||||
{
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin: 0px;
|
||||
}
|
||||
.TimelineRowCheck
|
||||
{
|
||||
/* Pull .TimelineRowExpand to the right of the checkbox */
|
||||
float:left;
|
||||
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.TimelineRowExpand
|
||||
{
|
||||
/* Pull .TimelineRowLabel to the right of +/- buttons */
|
||||
float:left;
|
||||
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.TimelineRowExpandButton
|
||||
{
|
||||
width: 11px;
|
||||
height: 12px;
|
||||
|
||||
color: #333;
|
||||
|
||||
border: 1px solid;
|
||||
|
||||
border-top-color:#F4F4F4;
|
||||
border-left-color:#F4F4F4;
|
||||
border-bottom-color:#8E8F8F;
|
||||
border-right-color:#8E8F8F;
|
||||
|
||||
/* Top-right to bottom-left grey background gradient */
|
||||
background: #f6f6f6; /* Old browsers */
|
||||
background: -moz-linear-gradient(-45deg, #f6f6f6 0%, #abaeb2 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#f6f6f6), color-stop(100%,#abaeb2)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* Opera 11.10+ */
|
||||
background: -ms-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* IE10+ */
|
||||
background: linear-gradient(135deg, #f6f6f6 0%,#abaeb2 100%); /* W3C */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f6f6f6', endColorstr='#abaeb2',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
|
||||
|
||||
text-align: center;
|
||||
vertical-align: center;
|
||||
}
|
||||
.TimelineRowExpandButton:hover
|
||||
{
|
||||
border-top-color:#79C6F9;
|
||||
border-left-color:#79C6F9;
|
||||
border-bottom-color:#385D72;
|
||||
border-right-color:#385D72;
|
||||
|
||||
/* Top-right to bottom-left blue background gradient, matching border */
|
||||
background: #f3f3f3; /* Old browsers */
|
||||
background: -moz-linear-gradient(-45deg, #f3f3f3 0%, #79c6f9 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#f3f3f3), color-stop(100%,#79c6f9)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* Opera 11.10+ */
|
||||
background: -ms-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* IE10+ */
|
||||
background: linear-gradient(135deg, #f3f3f3 0%,#79c6f9 100%); /* W3C */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f3f3f3', endColorstr='#79c6f9',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
|
||||
}
|
||||
.TimelineRowExpandButtonActive
|
||||
{
|
||||
/* Simple means of shifting text within a div to the bottom-right */
|
||||
padding-left:1px;
|
||||
padding-top:1px;
|
||||
width:10px;
|
||||
height:11px;
|
||||
}
|
||||
.TimelineRowLabel
|
||||
{
|
||||
/* Pull .TimelineRowCanvas to the right of the label */
|
||||
float:left;
|
||||
|
||||
width: 140px;
|
||||
height: 14px;
|
||||
}
|
||||
.TimelineRowCanvas
|
||||
{
|
||||
}
|
||||
|
||||
/* enable vertical scrollbar in TimelineContainer (useful for many threads) */
|
||||
.TimelineContainer
|
||||
{
|
||||
overflow-y: auto;
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
|
||||
//
|
||||
// Very basic linear value animation system, for now.
|
||||
//
|
||||
|
||||
|
||||
namespace("Anim");
|
||||
|
||||
|
||||
Anim.Animation = (function()
|
||||
{
|
||||
var anim_hz = 60;
|
||||
|
||||
|
||||
function Animation(anim_func, start_value, end_value, time, end_callback)
|
||||
{
|
||||
// Setup initial parameters
|
||||
this.StartValue = start_value;
|
||||
this.EndValue = end_value;
|
||||
this.ValueInc = (end_value - start_value) / (time * anim_hz);
|
||||
this.Value = start_value;
|
||||
this.Complete = false;
|
||||
this.EndCallback = end_callback;
|
||||
|
||||
// Cache the update function to prevent recreating the closure
|
||||
var self = this;
|
||||
this.AnimFunc = anim_func;
|
||||
this.AnimUpdate = function() { Update(self); }
|
||||
|
||||
// Call for the start value
|
||||
this.AnimUpdate();
|
||||
}
|
||||
|
||||
|
||||
function Update(self)
|
||||
{
|
||||
// Queue up the next frame immediately
|
||||
var id = window.setTimeout(self.AnimUpdate, 1000 / anim_hz);
|
||||
|
||||
// Linear step the value and check for completion
|
||||
self.Value += self.ValueInc;
|
||||
if (Math.abs(self.Value - self.EndValue) < 0.01)
|
||||
{
|
||||
self.Value = self.EndValue;
|
||||
self.Complete = true;
|
||||
|
||||
if (self.EndCallback)
|
||||
self.EndCallback();
|
||||
|
||||
window.clearTimeout(id);
|
||||
}
|
||||
|
||||
// Pass to the animation function
|
||||
self.AnimFunc(self.Value);
|
||||
}
|
||||
|
||||
|
||||
return Animation;
|
||||
})();
|
||||
|
||||
|
||||
Anim.Animate = function(anim_func, start_value, end_value, time, end_callback)
|
||||
{
|
||||
return new Anim.Animation(anim_func, start_value, end_value, time, end_callback);
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
//
|
||||
// This will generate a closure for the given function and optionally bind an arbitrary number of
|
||||
// its initial arguments to specific values.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// 0: Either the function scope or the function.
|
||||
// 1: If 0 is the function scope, this is the function.
|
||||
// Otherwise it's the start of the optional bound argument list.
|
||||
// 2: Start of the optional bound argument list if 1 is the function.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// function GlobalFunction(p0, p1, p2) { }
|
||||
// function ThisFunction(p0, p1, p2) { }
|
||||
//
|
||||
// var a = Bind("GlobalFunction");
|
||||
// var b = Bind(this, "ThisFunction");
|
||||
// var c = Bind("GlobalFunction", BoundParam0, BoundParam1);
|
||||
// var d = Bind(this, "ThisFunction", BoundParam0, BoundParam1);
|
||||
// var e = Bind(GlobalFunction);
|
||||
// var f = Bind(this, ThisFunction);
|
||||
// var g = Bind(GlobalFunction, BoundParam0, BoundParam1);
|
||||
// var h = Bind(this, ThisFunction, BoundParam0, BoundParam1);
|
||||
//
|
||||
// a(0, 1, 2);
|
||||
// b(0, 1, 2);
|
||||
// c(2);
|
||||
// d(2);
|
||||
// e(0, 1, 2);
|
||||
// f(0, 1, 2);
|
||||
// g(2);
|
||||
// h(2);
|
||||
//
|
||||
function Bind()
|
||||
{
|
||||
// No closure to define?
|
||||
if (arguments.length == 0)
|
||||
return null;
|
||||
|
||||
// Figure out which of the 4 call types is being used to bind
|
||||
// Locate scope, function and bound parameter start index
|
||||
|
||||
if (typeof(arguments[0]) == "string")
|
||||
{
|
||||
var scope = window;
|
||||
var func = window[arguments[0]];
|
||||
var start = 1;
|
||||
}
|
||||
|
||||
else if (typeof(arguments[0]) == "function")
|
||||
{
|
||||
var scope = window;
|
||||
var func = arguments[0];
|
||||
var start = 1;
|
||||
}
|
||||
|
||||
else if (typeof(arguments[1]) == "string")
|
||||
{
|
||||
var scope = arguments[0];
|
||||
var func = scope[arguments[1]];
|
||||
var start = 2;
|
||||
}
|
||||
|
||||
else if (typeof(arguments[1]) == "function")
|
||||
{
|
||||
var scope = arguments[0];
|
||||
var func = arguments[1];
|
||||
var start = 2;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// unknown
|
||||
console.log("Bind() ERROR: Unknown bind parameter configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the arguments list to an array
|
||||
var arg_array = Array.prototype.slice.call(arguments, start);
|
||||
start = arg_array.length;
|
||||
|
||||
return function()
|
||||
{
|
||||
// Concatenate incoming arguments
|
||||
for (var i = 0; i < arguments.length; i++)
|
||||
arg_array[start + i] = arguments[i];
|
||||
|
||||
// Call the function in the given scope with the new arguments
|
||||
return func.apply(scope, arg_array);
|
||||
}
|
||||
}
|
|
@ -1,218 +0,0 @@
|
|||
|
||||
namespace("Convert");
|
||||
|
||||
|
||||
//
|
||||
// Convert between utf8 and b64 without raising character out of range exceptions with unicode strings
|
||||
// Technique described here: http://monsur.hossa.in/2012/07/20/utf-8-in-javascript.html
|
||||
//
|
||||
Convert.utf8string_to_b64string = function(str)
|
||||
{
|
||||
return btoa(unescape(encodeURIComponent(str)));
|
||||
}
|
||||
Convert.b64string_to_utf8string = function(str)
|
||||
{
|
||||
return decodeURIComponent(escape(atob(str)));
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// More general approach, converting between byte arrays and b64
|
||||
// Info here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
|
||||
//
|
||||
Convert.b64string_to_Uint8Array = function(sBase64, nBlocksSize)
|
||||
{
|
||||
function b64ToUint6 (nChr)
|
||||
{
|
||||
return nChr > 64 && nChr < 91 ?
|
||||
nChr - 65
|
||||
: nChr > 96 && nChr < 123 ?
|
||||
nChr - 71
|
||||
: nChr > 47 && nChr < 58 ?
|
||||
nChr + 4
|
||||
: nChr === 43 ?
|
||||
62
|
||||
: nChr === 47 ?
|
||||
63
|
||||
:
|
||||
0;
|
||||
}
|
||||
|
||||
var
|
||||
sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""),
|
||||
nInLen = sB64Enc.length,
|
||||
nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2,
|
||||
taBytes = new Uint8Array(nOutLen);
|
||||
|
||||
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++)
|
||||
{
|
||||
nMod4 = nInIdx & 3;
|
||||
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
|
||||
if (nMod4 === 3 || nInLen - nInIdx === 1)
|
||||
{
|
||||
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++)
|
||||
taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return taBytes;
|
||||
}
|
||||
Convert.Uint8Array_to_b64string = function(aBytes)
|
||||
{
|
||||
function uint6ToB64 (nUint6)
|
||||
{
|
||||
return nUint6 < 26 ?
|
||||
nUint6 + 65
|
||||
: nUint6 < 52 ?
|
||||
nUint6 + 71
|
||||
: nUint6 < 62 ?
|
||||
nUint6 - 4
|
||||
: nUint6 === 62 ?
|
||||
43
|
||||
: nUint6 === 63 ?
|
||||
47
|
||||
:
|
||||
65;
|
||||
}
|
||||
|
||||
var nMod3, sB64Enc = "";
|
||||
|
||||
for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++)
|
||||
{
|
||||
nMod3 = nIdx % 3;
|
||||
if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0)
|
||||
sB64Enc += "\r\n";
|
||||
nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
|
||||
if (nMod3 === 2 || aBytes.length - nIdx === 1)
|
||||
{
|
||||
sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63));
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return sB64Enc.replace(/A(?=A$|$)/g, "=");
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Unicode and arbitrary value safe conversion between strings and Uint8Arrays
|
||||
//
|
||||
Convert.Uint8Array_to_string = function(aBytes)
|
||||
{
|
||||
var sView = "";
|
||||
|
||||
for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++)
|
||||
{
|
||||
nPart = aBytes[nIdx];
|
||||
sView += String.fromCharCode(
|
||||
nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */
|
||||
/* (nPart - 252 << 32) is not possible in ECMAScript! So...: */
|
||||
(nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
|
||||
: nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */
|
||||
(nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
|
||||
: nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */
|
||||
(nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
|
||||
: nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */
|
||||
(nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
|
||||
: nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */
|
||||
(nPart - 192 << 6) + aBytes[++nIdx] - 128
|
||||
: /* nPart < 127 ? */ /* one byte */
|
||||
nPart
|
||||
);
|
||||
}
|
||||
|
||||
return sView;
|
||||
}
|
||||
Convert.string_to_Uint8Array = function(sDOMStr)
|
||||
{
|
||||
var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0;
|
||||
|
||||
/* mapping... */
|
||||
|
||||
for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++)
|
||||
{
|
||||
nChr = sDOMStr.charCodeAt(nMapIdx);
|
||||
nArrLen += nChr < 0x80 ? 1 : nChr < 0x800 ? 2 : nChr < 0x10000 ? 3 : nChr < 0x200000 ? 4 : nChr < 0x4000000 ? 5 : 6;
|
||||
}
|
||||
|
||||
aBytes = new Uint8Array(nArrLen);
|
||||
|
||||
/* transcription... */
|
||||
|
||||
for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++)
|
||||
{
|
||||
nChr = sDOMStr.charCodeAt(nChrIdx);
|
||||
if (nChr < 128)
|
||||
{
|
||||
/* one byte */
|
||||
aBytes[nIdx++] = nChr;
|
||||
}
|
||||
else if (nChr < 0x800)
|
||||
{
|
||||
/* two bytes */
|
||||
aBytes[nIdx++] = 192 + (nChr >>> 6);
|
||||
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||
}
|
||||
else if (nChr < 0x10000)
|
||||
{
|
||||
/* three bytes */
|
||||
aBytes[nIdx++] = 224 + (nChr >>> 12);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||
}
|
||||
else if (nChr < 0x200000)
|
||||
{
|
||||
/* four bytes */
|
||||
aBytes[nIdx++] = 240 + (nChr >>> 18);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||
}
|
||||
else if (nChr < 0x4000000)
|
||||
{
|
||||
/* five bytes */
|
||||
aBytes[nIdx++] = 248 + (nChr >>> 24);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 18 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||
}
|
||||
else /* if (nChr <= 0x7fffffff) */
|
||||
{
|
||||
/* six bytes */
|
||||
aBytes[nIdx++] = 252 + /* (nChr >>> 32) is not possible in ECMAScript! So...: */ (nChr / 1073741824);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 24 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 18 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||
}
|
||||
}
|
||||
|
||||
return aBytes;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Converts all characters in a string that have equivalent entities to their ampersand/entity names.
|
||||
// Based on https://gist.github.com/jonathantneal/6093551
|
||||
//
|
||||
Convert.string_to_html_entities = (function()
|
||||
{
|
||||
'use strict';
|
||||
|
||||
var data = '34quot38amp39apos60lt62gt160nbsp161iexcl162cent163pound164curren165yen166brvbar167sect168uml169copy170ordf171laquo172not173shy174reg175macr176deg177plusmn178sup2179sup3180acute181micro182para183middot184cedil185sup1186ordm187raquo188frac14189frac12190frac34191iquest192Agrave193Aacute194Acirc195Atilde196Auml197Aring198AElig199Ccedil200Egrave201Eacute202Ecirc203Euml204Igrave205Iacute206Icirc207Iuml208ETH209Ntilde210Ograve211Oacute212Ocirc213Otilde214Ouml215times216Oslash217Ugrave218Uacute219Ucirc220Uuml221Yacute222THORN223szlig224agrave225aacute226acirc227atilde228auml229aring230aelig231ccedil232egrave233eacute234ecirc235euml236igrave237iacute238icirc239iuml240eth241ntilde242ograve243oacute244ocirc245otilde246ouml247divide248oslash249ugrave250uacute251ucirc252uuml253yacute254thorn255yuml402fnof913Alpha914Beta915Gamma916Delta917Epsilon918Zeta919Eta920Theta921Iota922Kappa923Lambda924Mu925Nu926Xi927Omicron928Pi929Rho931Sigma932Tau933Upsilon934Phi935Chi936Psi937Omega945alpha946beta947gamma948delta949epsilon950zeta951eta952theta953iota954kappa955lambda956mu957nu958xi959omicron960pi961rho962sigmaf963sigma964tau965upsilon966phi967chi968psi969omega977thetasym978upsih982piv8226bull8230hellip8242prime8243Prime8254oline8260frasl8472weierp8465image8476real8482trade8501alefsym8592larr8593uarr8594rarr8595darr8596harr8629crarr8656lArr8657uArr8658rArr8659dArr8660hArr8704forall8706part8707exist8709empty8711nabla8712isin8713notin8715ni8719prod8721sum8722minus8727lowast8730radic8733prop8734infin8736ang8743and8744or8745cap8746cup8747int8756there48764sim8773cong8776asymp8800ne8801equiv8804le8805ge8834sub8835sup8836nsub8838sube8839supe8853oplus8855otimes8869perp8901sdot8968lceil8969rceil8970lfloor8971rfloor9001lang9002rang9674loz9824spades9827clubs9829hearts9830diams338OElig339oelig352Scaron353scaron376Yuml710circ732tilde8194ensp8195emsp8201thinsp8204zwnj8205zwj8206lrm8207rlm8211ndash8212mdash8216lsquo8217rsquo8218sbquo8220ldquo8221rdquo8222bdquo8224dagger8225Dagger8240permil8249lsaquo8250rsaquo8364euro';
|
||||
var charCodes = data.split(/[A-z]+/);
|
||||
var entities = data.split(/\d+/).slice(1);
|
||||
|
||||
return function encodeHTMLEntities(text)
|
||||
{
|
||||
return text.replace(/[\u00A0-\u2666<>"'&]/g, function (match)
|
||||
{
|
||||
var charCode = String(match.charCodeAt(0));
|
||||
var index = charCodes.indexOf(charCode);
|
||||
return '&' + (entities[index] ? entities[index] : '#' + charCode) + ';';
|
||||
});
|
||||
};
|
||||
})();
|
|
@ -1,26 +0,0 @@
|
|||
|
||||
// TODO: requires function for checking existence of dependencies
|
||||
|
||||
|
||||
function namespace(name)
|
||||
{
|
||||
// Ensure all nested namespaces are created only once
|
||||
|
||||
var ns_list = name.split(".");
|
||||
var parent_ns = window;
|
||||
|
||||
for (var i in ns_list)
|
||||
{
|
||||
var ns_name = ns_list[i];
|
||||
if (!(ns_name in parent_ns))
|
||||
parent_ns[ns_name] = { };
|
||||
|
||||
parent_ns = parent_ns[ns_name];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function multiline(fn)
|
||||
{
|
||||
return fn.toString().split(/\n/).slice(1, -1).join("\n");
|
||||
}
|
|
@ -1,499 +0,0 @@
|
|||
|
||||
namespace("DOM.Node");
|
||||
namespace("DOM.Event");
|
||||
namespace("DOM.Applet");
|
||||
|
||||
|
||||
|
||||
//
|
||||
// =====================================================================================================================
|
||||
// ----- DOCUMENT NODE/ELEMENT EXTENSIONS ------------------------------------------------------------------------------
|
||||
// =====================================================================================================================
|
||||
//
|
||||
|
||||
|
||||
|
||||
DOM.Node.Get = function(id)
|
||||
{
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Set node position
|
||||
//
|
||||
DOM.Node.SetPosition = function(node, position)
|
||||
{
|
||||
node.style.left = position[0];
|
||||
node.style.top = position[1];
|
||||
}
|
||||
DOM.Node.SetX = function(node, x)
|
||||
{
|
||||
node.style.left = x;
|
||||
}
|
||||
DOM.Node.SetY = function(node, y)
|
||||
{
|
||||
node.style.top = y;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Get the absolute position of a HTML element on the page
|
||||
//
|
||||
DOM.Node.GetPosition = function(element, account_for_scroll)
|
||||
{
|
||||
// Recurse up through parents, summing offsets from their parent
|
||||
var x = 0, y = 0;
|
||||
for (var node = element; node != null; node = node.offsetParent)
|
||||
{
|
||||
x += node.offsetLeft;
|
||||
y += node.offsetTop;
|
||||
}
|
||||
|
||||
if (account_for_scroll)
|
||||
{
|
||||
// Walk up the hierarchy subtracting away any scrolling
|
||||
for (var node = element; node != document.body; node = node.parentNode)
|
||||
{
|
||||
x -= node.scrollLeft;
|
||||
y -= node.scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Set node size
|
||||
//
|
||||
DOM.Node.SetSize = function(node, size)
|
||||
{
|
||||
node.style.width = size[0];
|
||||
node.style.height = size[1];
|
||||
}
|
||||
DOM.Node.SetWidth = function(node, width)
|
||||
{
|
||||
node.style.width = width;
|
||||
}
|
||||
DOM.Node.SetHeight = function(node, height)
|
||||
{
|
||||
node.style.height = height;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Get node OFFSET size:
|
||||
// clientX includes padding
|
||||
// offsetX includes padding and borders
|
||||
// scrollX includes padding, borders and size of contained node
|
||||
//
|
||||
DOM.Node.GetSize = function(node)
|
||||
{
|
||||
return [ node.offsetWidth, node.offsetHeight ];
|
||||
}
|
||||
DOM.Node.GetWidth = function(node)
|
||||
{
|
||||
return node.offsetWidth;
|
||||
}
|
||||
DOM.Node.GetHeight = function(node)
|
||||
{
|
||||
return node.offsetHeight;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Set node opacity
|
||||
//
|
||||
DOM.Node.SetOpacity = function(node, value)
|
||||
{
|
||||
node.style.opacity = value;
|
||||
}
|
||||
|
||||
|
||||
DOM.Node.SetColour = function(node, colour)
|
||||
{
|
||||
node.style.color = colour;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Hide a node by completely disabling its rendering (it no longer contributes to document layout)
|
||||
//
|
||||
DOM.Node.Hide = function(node)
|
||||
{
|
||||
node.style.display = "none";
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Show a node by restoring its influcen in document layout
|
||||
//
|
||||
DOM.Node.Show = function(node)
|
||||
{
|
||||
node.style.display = "block";
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Add a CSS class to a HTML element, specified last
|
||||
//
|
||||
DOM.Node.AddClass = function(node, class_name)
|
||||
{
|
||||
// Ensure the class hasn't already been added
|
||||
DOM.Node.RemoveClass(node, class_name);
|
||||
node.className += " " + class_name;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Remove a CSS class from a HTML element
|
||||
//
|
||||
DOM.Node.RemoveClass = function(node, class_name)
|
||||
{
|
||||
// Remove all variations of where the class name can be in the string list
|
||||
var regexp = new RegExp("\\b" + class_name + "\\b");
|
||||
node.className = node.className.replace(regexp, "");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Check to see if a HTML element contains a class
|
||||
//
|
||||
DOM.Node.HasClass = function(node, class_name)
|
||||
{
|
||||
var regexp = new RegExp("\\b" + class_name + "\\b");
|
||||
return regexp.test(node.className);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Recursively search for a node with the given class name
|
||||
//
|
||||
DOM.Node.FindWithClass = function(parent_node, class_name, index)
|
||||
{
|
||||
// Search the children looking for a node with the given class name
|
||||
for (var i in parent_node.childNodes)
|
||||
{
|
||||
var node = parent_node.childNodes[i];
|
||||
if (DOM.Node.HasClass(node, class_name))
|
||||
{
|
||||
if (index === undefined || index-- == 0)
|
||||
return node;
|
||||
}
|
||||
|
||||
// Recurse into children
|
||||
node = DOM.Node.FindWithClass(node, class_name);
|
||||
if (node != null)
|
||||
return node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Check to see if one node logically contains another
|
||||
//
|
||||
DOM.Node.Contains = function(node, container_node)
|
||||
{
|
||||
while (node != null && node != container_node)
|
||||
node = node.parentNode;
|
||||
return node != null;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Create the HTML nodes specified in the text passed in
|
||||
// Assumes there is only one root node in the text
|
||||
//
|
||||
DOM.Node.CreateHTML = function(html)
|
||||
{
|
||||
var div = document.createElement("div");
|
||||
div.innerHTML = html;
|
||||
|
||||
// First child may be a text node, followed by the created HTML
|
||||
var child = div.firstChild;
|
||||
if (child != null && child.nodeType == 3)
|
||||
child = child.nextSibling;
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Make a copy of a HTML element, making it visible and clearing its ID to ensure it's not a duplicate
|
||||
//
|
||||
DOM.Node.Clone = function(name)
|
||||
{
|
||||
// Get the template element and clone it, making sure it's renderable
|
||||
var node = DOM.Node.Get(name);
|
||||
node = node.cloneNode(true);
|
||||
node.id = null;
|
||||
node.style.display = "block";
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Append an arbitrary block of HTML to an existing node
|
||||
//
|
||||
DOM.Node.AppendHTML = function(node, html)
|
||||
{
|
||||
var child = DOM.Node.CreateHTML(html);
|
||||
node.appendChild(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Append a div that clears the float style
|
||||
//
|
||||
DOM.Node.AppendClearFloat = function(node)
|
||||
{
|
||||
var div = document.createElement("div");
|
||||
div.style.clear = "both";
|
||||
node.appendChild(div);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Check to see that the object passed in is an instance of a DOM node
|
||||
//
|
||||
DOM.Node.IsNode = function(object)
|
||||
{
|
||||
return object instanceof Element;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Create an "iframe shim" so that elements within it render over a Java Applet
|
||||
// http://web.archive.org/web/20110707212850/http://www.oratransplant.nl/2007/10/26/using-iframe-shim-to-partly-cover-a-java-applet/
|
||||
//
|
||||
DOM.Node.CreateShim = function(parent)
|
||||
{
|
||||
var shimmer = document.createElement("iframe");
|
||||
|
||||
// Position the shimmer so that it's the same location/size as its parent
|
||||
shimmer.style.position = "fixed";
|
||||
shimmer.style.left = parent.style.left;
|
||||
shimmer.style.top = parent.style.top;
|
||||
shimmer.style.width = parent.offsetWidth;
|
||||
shimmer.style.height = parent.offsetHeight;
|
||||
|
||||
// We want the shimmer to be one level below its contents
|
||||
shimmer.style.zIndex = parent.style.zIndex - 1;
|
||||
|
||||
// Ensure its empty
|
||||
shimmer.setAttribute("frameborder", "0");
|
||||
shimmer.setAttribute("src", "");
|
||||
|
||||
// Add to the document and the parent
|
||||
document.body.appendChild(shimmer);
|
||||
parent.Shimmer = shimmer;
|
||||
return shimmer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// =====================================================================================================================
|
||||
// ----- EVENT HANDLING EXTENSIONS -------------------------------------------------------------------------------------
|
||||
// =====================================================================================================================
|
||||
//
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Retrieves the event from the first parameter passed into an HTML event
|
||||
//
|
||||
DOM.Event.Get = function(evt)
|
||||
{
|
||||
// Internet explorer doesn't pass the event
|
||||
return window.event || evt;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Retrieves the element that triggered an event from the event object
|
||||
//
|
||||
DOM.Event.GetNode = function(evt)
|
||||
{
|
||||
evt = DOM.Event.Get(evt);
|
||||
|
||||
// Get the target element
|
||||
var element;
|
||||
if (evt.target)
|
||||
element = evt.target;
|
||||
else if (e.srcElement)
|
||||
element = evt.srcElement;
|
||||
|
||||
// Default Safari bug
|
||||
if (element.nodeType == 3)
|
||||
element = element.parentNode;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Stop default action for an event
|
||||
//
|
||||
DOM.Event.StopDefaultAction = function(evt)
|
||||
{
|
||||
if (evt && evt.preventDefault)
|
||||
evt.preventDefault();
|
||||
else if (window.event && window.event.returnValue)
|
||||
window.event.returnValue = false;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Stops events bubbling up to parent event handlers
|
||||
//
|
||||
DOM.Event.StopPropagation = function(evt)
|
||||
{
|
||||
evt = DOM.Event.Get(evt);
|
||||
if (evt)
|
||||
{
|
||||
evt.cancelBubble = true;
|
||||
if (evt.stopPropagation)
|
||||
evt.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Stop both event default action and propagation
|
||||
//
|
||||
DOM.Event.StopAll = function(evt)
|
||||
{
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
DOM.Event.StopPropagation(evt);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Adds an event handler to an event
|
||||
//
|
||||
DOM.Event.AddHandler = function(obj, evt, func)
|
||||
{
|
||||
if (obj)
|
||||
{
|
||||
if (obj.addEventListener)
|
||||
obj.addEventListener(evt, func, false);
|
||||
else if (obj.attachEvent)
|
||||
obj.attachEvent("on" + evt, func);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Removes an event handler from an event
|
||||
//
|
||||
DOM.Event.RemoveHandler = function(obj, evt, func)
|
||||
{
|
||||
if (obj)
|
||||
{
|
||||
if (obj.removeEventListener)
|
||||
obj.removeEventListener(evt, func, false);
|
||||
else if (obj.detachEvent)
|
||||
obj.detachEvent("on" + evt, func);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Get the position of the mouse cursor, page relative
|
||||
//
|
||||
DOM.Event.GetMousePosition = function(evt)
|
||||
{
|
||||
evt = DOM.Event.Get(evt);
|
||||
|
||||
var px = 0;
|
||||
var py = 0;
|
||||
if (evt.pageX || evt.pageY)
|
||||
{
|
||||
px = evt.pageX;
|
||||
py = evt.pageY;
|
||||
}
|
||||
else if (evt.clientX || evt.clientY)
|
||||
{
|
||||
px = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
|
||||
py = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
|
||||
}
|
||||
|
||||
return [px, py];
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// =====================================================================================================================
|
||||
// ----- JAVA APPLET EXTENSIONS ----------------------------------------------------------------------------------------
|
||||
// =====================================================================================================================
|
||||
//
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Create an applet element for loading a Java applet, attaching it to the specified node
|
||||
//
|
||||
DOM.Applet.Load = function(dest_id, id, code, archive)
|
||||
{
|
||||
// Lookup the applet destination
|
||||
var dest = DOM.Node.Get(dest_id);
|
||||
if (!dest)
|
||||
return;
|
||||
|
||||
// Construct the applet element and add it to the destination
|
||||
Debug.Log("Injecting applet DOM code");
|
||||
var applet = "<applet id='" + id + "' code='" + code + "' archive='" + archive + "'";
|
||||
applet += " width='" + dest.offsetWidth + "' height='" + dest.offsetHeight + "'>";
|
||||
applet += "</applet>";
|
||||
dest.innerHTML = applet;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Moves and resizes a named applet so that it fits in the destination div element.
|
||||
// The applet must be contained by a div element itself. This container div is moved along
|
||||
// with the applet.
|
||||
//
|
||||
DOM.Applet.Move = function(dest_div, applet, z_index, hide)
|
||||
{
|
||||
if (!applet || !dest_div)
|
||||
return;
|
||||
|
||||
// Before modifying any location information, hide the applet so that it doesn't render over
|
||||
// any newly visible elements that appear while the location information is being modified.
|
||||
if (hide)
|
||||
applet.style.visibility = "hidden";
|
||||
|
||||
// Get its view rect
|
||||
var pos = DOM.Node.GetPosition(dest_div);
|
||||
var w = dest_div.offsetWidth;
|
||||
var h = dest_div.offsetHeight;
|
||||
|
||||
// It needs to be embedded in a <div> for correct scale/position adjustment
|
||||
var container = applet.parentNode;
|
||||
if (!container || container.localName != "div")
|
||||
{
|
||||
Debug.Log("ERROR: Couldn't find source applet's div container");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reposition and resize the containing div element
|
||||
container.style.left = pos[0];
|
||||
container.style.top = pos[1];
|
||||
container.style.width = w;
|
||||
container.style.height = h;
|
||||
container.style.zIndex = z_index;
|
||||
|
||||
// Resize the applet itself
|
||||
applet.style.width = w;
|
||||
applet.style.height = h;
|
||||
|
||||
// Everything modified, safe to show
|
||||
applet.style.visibility = "visible";
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
|
||||
namespace("Keyboard")
|
||||
|
||||
|
||||
// =====================================================================================================================
|
||||
// Key codes copied from closure-library
|
||||
// https://code.google.com/p/closure-library/source/browse/closure/goog/events/keycodes.js
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Keyboard.Codes = {
|
||||
WIN_KEY_FF_LINUX : 0,
|
||||
MAC_ENTER : 3,
|
||||
BACKSPACE : 8,
|
||||
TAB : 9,
|
||||
NUM_CENTER : 12, // NUMLOCK on FF/Safari Mac
|
||||
ENTER : 13,
|
||||
SHIFT : 16,
|
||||
CTRL : 17,
|
||||
ALT : 18,
|
||||
PAUSE : 19,
|
||||
CAPS_LOCK : 20,
|
||||
ESC : 27,
|
||||
SPACE : 32,
|
||||
PAGE_UP : 33, // also NUM_NORTH_EAST
|
||||
PAGE_DOWN : 34, // also NUM_SOUTH_EAST
|
||||
END : 35, // also NUM_SOUTH_WEST
|
||||
HOME : 36, // also NUM_NORTH_WEST
|
||||
LEFT : 37, // also NUM_WEST
|
||||
UP : 38, // also NUM_NORTH
|
||||
RIGHT : 39, // also NUM_EAST
|
||||
DOWN : 40, // also NUM_SOUTH
|
||||
PRINT_SCREEN : 44,
|
||||
INSERT : 45, // also NUM_INSERT
|
||||
DELETE : 46, // also NUM_DELETE
|
||||
ZERO : 48,
|
||||
ONE : 49,
|
||||
TWO : 50,
|
||||
THREE : 51,
|
||||
FOUR : 52,
|
||||
FIVE : 53,
|
||||
SIX : 54,
|
||||
SEVEN : 55,
|
||||
EIGHT : 56,
|
||||
NINE : 57,
|
||||
FF_SEMICOLON : 59, // Firefox (Gecko) fires this for semicolon instead of 186
|
||||
FF_EQUALS : 61, // Firefox (Gecko) fires this for equals instead of 187
|
||||
FF_DASH : 173, // Firefox (Gecko) fires this for dash instead of 189
|
||||
QUESTION_MARK : 63, // needs localization
|
||||
A : 65,
|
||||
B : 66,
|
||||
C : 67,
|
||||
D : 68,
|
||||
E : 69,
|
||||
F : 70,
|
||||
G : 71,
|
||||
H : 72,
|
||||
I : 73,
|
||||
J : 74,
|
||||
K : 75,
|
||||
L : 76,
|
||||
M : 77,
|
||||
N : 78,
|
||||
O : 79,
|
||||
P : 80,
|
||||
Q : 81,
|
||||
R : 82,
|
||||
S : 83,
|
||||
T : 84,
|
||||
U : 85,
|
||||
V : 86,
|
||||
W : 87,
|
||||
X : 88,
|
||||
Y : 89,
|
||||
Z : 90,
|
||||
META : 91, // WIN_KEY_LEFT
|
||||
WIN_KEY_RIGHT : 92,
|
||||
CONTEXT_MENU : 93,
|
||||
NUM_ZERO : 96,
|
||||
NUM_ONE : 97,
|
||||
NUM_TWO : 98,
|
||||
NUM_THREE : 99,
|
||||
NUM_FOUR : 100,
|
||||
NUM_FIVE : 101,
|
||||
NUM_SIX : 102,
|
||||
NUM_SEVEN : 103,
|
||||
NUM_EIGHT : 104,
|
||||
NUM_NINE : 105,
|
||||
NUM_MULTIPLY : 106,
|
||||
NUM_PLUS : 107,
|
||||
NUM_MINUS : 109,
|
||||
NUM_PERIOD : 110,
|
||||
NUM_DIVISION : 111,
|
||||
F1 : 112,
|
||||
F2 : 113,
|
||||
F3 : 114,
|
||||
F4 : 115,
|
||||
F5 : 116,
|
||||
F6 : 117,
|
||||
F7 : 118,
|
||||
F8 : 119,
|
||||
F9 : 120,
|
||||
F10 : 121,
|
||||
F11 : 122,
|
||||
F12 : 123,
|
||||
NUMLOCK : 144,
|
||||
SCROLL_LOCK : 145,
|
||||
|
||||
// OS-specific media keys like volume controls and browser controls.
|
||||
FIRST_MEDIA_KEY : 166,
|
||||
LAST_MEDIA_KEY : 183,
|
||||
|
||||
SEMICOLON : 186, // needs localization
|
||||
DASH : 189, // needs localization
|
||||
EQUALS : 187, // needs localization
|
||||
COMMA : 188, // needs localization
|
||||
PERIOD : 190, // needs localization
|
||||
SLASH : 191, // needs localization
|
||||
APOSTROPHE : 192, // needs localization
|
||||
TILDE : 192, // needs localization
|
||||
SINGLE_QUOTE : 222, // needs localization
|
||||
OPEN_SQUARE_BRACKET : 219, // needs localization
|
||||
BACKSLASH : 220, // needs localization
|
||||
CLOSE_SQUARE_BRACKET: 221, // needs localization
|
||||
WIN_KEY : 224,
|
||||
MAC_FF_META : 224, // Firefox (Gecko) fires this for the meta key instead of 91
|
||||
MAC_WK_CMD_LEFT : 91, // WebKit Left Command key fired, same as META
|
||||
MAC_WK_CMD_RIGHT : 93, // WebKit Right Command key fired, different from META
|
||||
WIN_IME : 229,
|
||||
|
||||
// We've seen users whose machines fire this keycode at regular one
|
||||
// second intervals. The common thread among these users is that
|
||||
// they're all using Dell Inspiron laptops, so we suspect that this
|
||||
// indicates a hardware/bios problem.
|
||||
// http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx
|
||||
PHANTOM : 255
|
||||
};
|
||||
// =====================================================================================================================
|
|
@ -1,40 +0,0 @@
|
|||
|
||||
namespace("LocalStore");
|
||||
|
||||
|
||||
LocalStore.Set = function(class_name, class_id, variable_id, data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (typeof(Storage) != "undefined")
|
||||
{
|
||||
var name = class_name + "_" + class_id + "_" + variable_id;
|
||||
localStorage[name] = JSON.stringify(data);
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log("Local Storage Set Error: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LocalStore.Get = function(class_name, class_id, variable_id, default_data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (typeof(Storage) != "undefined")
|
||||
{
|
||||
var name = class_name + "_" + class_id + "_" + variable_id;
|
||||
var data = localStorage[name]
|
||||
if (data)
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log("Local Storage Get Error: " + e.message);
|
||||
}
|
||||
|
||||
return default_data;
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
|
||||
namespace("Mouse");
|
||||
|
||||
|
||||
Mouse.State =(function()
|
||||
{
|
||||
function State(event)
|
||||
{
|
||||
// Get button press states
|
||||
if (typeof event.buttons != "undefined")
|
||||
{
|
||||
// Firefox
|
||||
this.Left = (event.buttons & 1) != 0;
|
||||
this.Right = (event.buttons & 2) != 0;
|
||||
this.Middle = (event.buttons & 4) != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chrome
|
||||
this.Left = (event.button == 0);
|
||||
this.Middle = (event.button == 1);
|
||||
this.Right = (event.button == 2);
|
||||
}
|
||||
|
||||
// Get page-relative mouse position
|
||||
this.Position = DOM.Event.GetMousePosition(event);
|
||||
|
||||
// Get wheel delta
|
||||
var delta = 0;
|
||||
if (event.wheelDelta)
|
||||
delta = event.wheelDelta / 120; // IE/Opera
|
||||
else if (event.detail)
|
||||
delta = -event.detail / 3; // Mozilla
|
||||
this.WheelDelta = delta;
|
||||
|
||||
// Get the mouse position delta
|
||||
// Requires Pointer Lock API support
|
||||
this.PositionDelta = [
|
||||
event.movementX || event.mozMovementX || event.webkitMovementX || 0,
|
||||
event.movementY || event.mozMovementY || event.webkitMovementY || 0
|
||||
];
|
||||
}
|
||||
|
||||
return State;
|
||||
})();
|
||||
|
||||
|
||||
//
|
||||
// Basic Pointer Lock API support
|
||||
// https://developer.mozilla.org/en-US/docs/WebAPI/Pointer_Lock
|
||||
// http://www.chromium.org/developers/design-documents/mouse-lock
|
||||
//
|
||||
// Note that API has not been standardised yet so browsers can implement functions with prefixes
|
||||
//
|
||||
|
||||
|
||||
Mouse.PointerLockSupported = function()
|
||||
{
|
||||
return 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
|
||||
}
|
||||
|
||||
|
||||
Mouse.RequestPointerLock = function(element)
|
||||
{
|
||||
element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;
|
||||
if (element.requestPointerLock)
|
||||
element.requestPointerLock();
|
||||
}
|
||||
|
||||
|
||||
Mouse.ExitPointerLock = function()
|
||||
{
|
||||
document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock;
|
||||
if (document.exitPointerLock)
|
||||
document.exitPointerLock();
|
||||
}
|
||||
|
||||
|
||||
// Can use this element to detect whether pointer lock is enabled (returns non-null)
|
||||
Mouse.PointerLockElement = function()
|
||||
{
|
||||
return document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement;
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
|
||||
namespace("Hash");
|
||||
|
||||
/**
|
||||
* JS Implementation of MurmurHash3 (r136) (as of May 20, 2011)
|
||||
*
|
||||
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
|
||||
* @see http://github.com/garycourt/murmurhash-js
|
||||
* @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
|
||||
* @see http://sites.google.com/site/murmurhash/
|
||||
*
|
||||
* @param {string} key ASCII only
|
||||
* @param {number} seed Positive integer only
|
||||
* @return {number} 32-bit positive integer hash
|
||||
*/
|
||||
|
||||
Hash.Murmur3 = function(key, seed)
|
||||
{
|
||||
var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i;
|
||||
|
||||
remainder = key.length & 3; // key.length % 4
|
||||
bytes = key.length - remainder;
|
||||
h1 = seed;
|
||||
c1 = 0xcc9e2d51;
|
||||
c2 = 0x1b873593;
|
||||
i = 0;
|
||||
|
||||
while (i < bytes) {
|
||||
k1 =
|
||||
((key.charCodeAt(i) & 0xff)) |
|
||||
((key.charCodeAt(++i) & 0xff) << 8) |
|
||||
((key.charCodeAt(++i) & 0xff) << 16) |
|
||||
((key.charCodeAt(++i) & 0xff) << 24);
|
||||
++i;
|
||||
|
||||
k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff;
|
||||
k1 = (k1 << 15) | (k1 >>> 17);
|
||||
k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff;
|
||||
|
||||
h1 ^= k1;
|
||||
h1 = (h1 << 13) | (h1 >>> 19);
|
||||
h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff;
|
||||
h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16));
|
||||
}
|
||||
|
||||
k1 = 0;
|
||||
|
||||
switch (remainder) {
|
||||
case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
|
||||
case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
|
||||
case 1: k1 ^= (key.charCodeAt(i) & 0xff);
|
||||
|
||||
k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
|
||||
k1 = (k1 << 15) | (k1 >>> 17);
|
||||
k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
|
||||
h1 ^= k1;
|
||||
}
|
||||
|
||||
h1 ^= key.length;
|
||||
|
||||
h1 ^= h1 >>> 16;
|
||||
h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
|
||||
h1 ^= h1 >>> 13;
|
||||
h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff;
|
||||
h1 ^= h1 >>> 16;
|
||||
|
||||
return h1 >>> 0;
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
|
||||
namespace("WM");
|
||||
|
||||
|
||||
WM.Button = (function()
|
||||
{
|
||||
var template_html = "<div class='Button notextsel'></div>";
|
||||
|
||||
|
||||
function Button(text, x, y, opts)
|
||||
{
|
||||
this.OnClick = null;
|
||||
this.Toggle = opts && opts.toggle;
|
||||
|
||||
this.Node = DOM.Node.CreateHTML(template_html);
|
||||
|
||||
// Set node dimensions
|
||||
this.SetPosition(x, y);
|
||||
if (opts && opts.w && opts.h)
|
||||
this.SetSize(opts.w, opts.h);
|
||||
|
||||
// Override the default class name
|
||||
if (opts && opts.class)
|
||||
this.Node.className = opts.class;
|
||||
|
||||
this.SetText(text);
|
||||
|
||||
// Create the mouse press event handlers
|
||||
DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this));
|
||||
this.OnMouseOutDelegate = Bind(OnMouseUp, this, false);
|
||||
this.OnMouseUpDelegate = Bind(OnMouseUp, this, true);
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.SetPosition = function(x, y)
|
||||
{
|
||||
this.Position = [ x, y ];
|
||||
DOM.Node.SetPosition(this.Node, this.Position);
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.SetSize = function(w, h)
|
||||
{
|
||||
this.Size = [ w, h ];
|
||||
DOM.Node.SetSize(this.Node, this.Size);
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.SetText = function(text)
|
||||
{
|
||||
this.Node.innerHTML = text;
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.SetOnClick = function(on_click)
|
||||
{
|
||||
this.OnClick = on_click;
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.SetState = function(pressed)
|
||||
{
|
||||
if (pressed)
|
||||
DOM.Node.AddClass(this.Node, "ButtonHeld");
|
||||
else
|
||||
DOM.Node.RemoveClass(this.Node, "ButtonHeld");
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.ToggleState = function()
|
||||
{
|
||||
if (DOM.Node.HasClass(this.Node, "ButtonHeld"))
|
||||
this.SetState(false);
|
||||
else
|
||||
this.SetState(true);
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.IsPressed = function()
|
||||
{
|
||||
return DOM.Node.HasClass(this.Node, "ButtonHeld");
|
||||
}
|
||||
|
||||
|
||||
function OnMouseDown(self, evt)
|
||||
{
|
||||
// Decide how to set the button state
|
||||
if (self.Toggle)
|
||||
self.ToggleState();
|
||||
else
|
||||
self.SetState(true);
|
||||
|
||||
// Activate release handlers
|
||||
DOM.Event.AddHandler(self.Node, "mouseout", self.OnMouseOutDelegate);
|
||||
DOM.Event.AddHandler(self.Node, "mouseup", self.OnMouseUpDelegate);
|
||||
|
||||
DOM.Event.StopAll(evt);
|
||||
}
|
||||
|
||||
|
||||
function OnMouseUp(self, confirm, evt)
|
||||
{
|
||||
if (confirm)
|
||||
{
|
||||
// Only release for non-toggles
|
||||
if (!self.Toggle)
|
||||
self.SetState(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Decide how to set the button state
|
||||
if (self.Toggle)
|
||||
self.ToggleState();
|
||||
else
|
||||
self.SetState(false);
|
||||
}
|
||||
|
||||
// Remove release handlers
|
||||
DOM.Event.RemoveHandler(self.Node, "mouseout", self.OnMouseOutDelegate);
|
||||
DOM.Event.RemoveHandler(self.Node, "mouseup", self.OnMouseUpDelegate);
|
||||
|
||||
// Call the click handler if this is a button press
|
||||
if (confirm && self.OnClick)
|
||||
self.OnClick(self);
|
||||
|
||||
DOM.Event.StopAll(evt);
|
||||
}
|
||||
|
||||
|
||||
return Button;
|
||||
})();
|
|
@ -1,237 +0,0 @@
|
|||
|
||||
namespace("WM");
|
||||
|
||||
|
||||
WM.ComboBoxPopup = (function()
|
||||
{
|
||||
var body_template_html = "<div class='ComboBoxPopup'></div>";
|
||||
|
||||
var item_template_html = " \
|
||||
<div class='ComboBoxPopupItem notextsel'> \
|
||||
<div class='ComboBoxPopupItemText'></div> \
|
||||
<div class='ComboBoxPopupItemIcon'><img src='BrowserLibImages/tick.gif'></div> \
|
||||
<div style='clear:both'></div> \
|
||||
</div>";
|
||||
|
||||
|
||||
function ComboBoxPopup(combo_box)
|
||||
{
|
||||
this.ComboBox = combo_box;
|
||||
this.ParentNode = combo_box.Node;
|
||||
this.ValueNodes = [ ];
|
||||
|
||||
// Create the template node
|
||||
this.Node = DOM.Node.CreateHTML(body_template_html);
|
||||
|
||||
DOM.Event.AddHandler(this.Node, "mousedown", Bind(SelectItem, this));
|
||||
this.CancelDelegate = Bind(this, "Cancel");
|
||||
}
|
||||
|
||||
|
||||
ComboBoxPopup.prototype.SetValues = function(values)
|
||||
{
|
||||
// Clear existing values
|
||||
this.Node.innerHTML = "";
|
||||
|
||||
// Generate HTML nodes for each value
|
||||
this.ValueNodes = [ ];
|
||||
for (var i in values)
|
||||
{
|
||||
var item_node = DOM.Node.CreateHTML(item_template_html);
|
||||
var text_node = DOM.Node.FindWithClass(item_node, "ComboBoxPopupItemText");
|
||||
|
||||
item_node.Value = values[i];
|
||||
text_node.innerHTML = values[i];
|
||||
|
||||
this.Node.appendChild(item_node);
|
||||
this.ValueNodes.push(item_node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ComboBoxPopup.prototype.Show = function(selection_index)
|
||||
{
|
||||
// Initially match the position of the parent node
|
||||
var pos = DOM.Node.GetPosition(this.ParentNode);
|
||||
DOM.Node.SetPosition(this.Node, pos);
|
||||
|
||||
// Take the width/z-index from the parent node
|
||||
this.Node.style.width = this.ParentNode.offsetWidth;
|
||||
this.Node.style.zIndex = this.ParentNode.style.zIndex + 1;
|
||||
|
||||
// Setup event handlers
|
||||
DOM.Event.AddHandler(document.body, "mousedown", this.CancelDelegate);
|
||||
|
||||
// Show the popup so that the HTML layout engine kicks in before
|
||||
// the layout info is used below
|
||||
this.ParentNode.appendChild(this.Node);
|
||||
|
||||
// Show/hide the tick image based on which node is selected
|
||||
for (var i in this.ValueNodes)
|
||||
{
|
||||
var node = this.ValueNodes[i];
|
||||
var icon_node = DOM.Node.FindWithClass(node, "ComboBoxPopupItemIcon");
|
||||
|
||||
if (i == selection_index)
|
||||
{
|
||||
icon_node.style.display = "block";
|
||||
|
||||
// Also, shift the popup up so that the mouse is over the selected item and is highlighted
|
||||
var item_pos = DOM.Node.GetPosition(this.ValueNodes[selection_index]);
|
||||
var diff_pos = [ item_pos[0] - pos[0], item_pos[1] - pos[1] ];
|
||||
pos = [ pos[0] - diff_pos[0], pos[1] - diff_pos[1] ];
|
||||
}
|
||||
else
|
||||
{
|
||||
icon_node.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
DOM.Node.SetPosition(this.Node, pos);
|
||||
}
|
||||
|
||||
|
||||
ComboBoxPopup.prototype.Hide = function()
|
||||
{
|
||||
DOM.Event.RemoveHandler(document.body, "mousedown", this.CancelDelegate);
|
||||
this.ParentNode.removeChild(this.Node);
|
||||
}
|
||||
|
||||
|
||||
function SelectItem(self, evt)
|
||||
{
|
||||
// Search for which item node is being clicked on
|
||||
var node = DOM.Event.GetNode(evt);
|
||||
for (var i in self.ValueNodes)
|
||||
{
|
||||
var value_node = self.ValueNodes[i];
|
||||
if (DOM.Node.Contains(node, value_node))
|
||||
{
|
||||
// Set the value on the combo box
|
||||
self.ComboBox.SetValue(value_node.Value);
|
||||
self.Hide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Cancel(self, evt)
|
||||
{
|
||||
// Don't cancel if the mouse up is anywhere on the popup or combo box
|
||||
var node = DOM.Event.GetNode(evt);
|
||||
if (!DOM.Node.Contains(node, self.Node) &&
|
||||
!DOM.Node.Contains(node, self.ParentNode))
|
||||
{
|
||||
self.Hide();
|
||||
}
|
||||
|
||||
|
||||
DOM.Event.StopAll(evt);
|
||||
}
|
||||
|
||||
|
||||
return ComboBoxPopup;
|
||||
})();
|
||||
|
||||
|
||||
WM.ComboBox = (function()
|
||||
{
|
||||
var template_html = " \
|
||||
<div class='ComboBox'> \
|
||||
<div class='ComboBoxText notextsel'></div> \
|
||||
<div class='ComboBoxIcon'><img src='BrowserLibImages/up_down.gif'></div> \
|
||||
<div style='clear:both'></div> \
|
||||
</div>";
|
||||
|
||||
|
||||
function ComboBox()
|
||||
{
|
||||
this.OnChange = null;
|
||||
|
||||
// Create the template node and locate key nodes
|
||||
this.Node = DOM.Node.CreateHTML(template_html);
|
||||
this.TextNode = DOM.Node.FindWithClass(this.Node, "ComboBoxText");
|
||||
|
||||
// Create a reusable popup
|
||||
this.Popup = new WM.ComboBoxPopup(this);
|
||||
|
||||
// Set an empty set of values
|
||||
this.SetValues([]);
|
||||
this.SetValue("<empty>");
|
||||
|
||||
// Create the mouse press event handlers
|
||||
DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this));
|
||||
this.OnMouseOutDelegate = Bind(OnMouseUp, this, false);
|
||||
this.OnMouseUpDelegate = Bind(OnMouseUp, this, true);
|
||||
}
|
||||
|
||||
|
||||
ComboBox.prototype.SetOnChange = function(on_change)
|
||||
{
|
||||
this.OnChange = on_change;
|
||||
}
|
||||
|
||||
|
||||
ComboBox.prototype.SetValues = function(values)
|
||||
{
|
||||
this.Values = values;
|
||||
this.Popup.SetValues(values);
|
||||
}
|
||||
|
||||
|
||||
ComboBox.prototype.SetValue = function(value)
|
||||
{
|
||||
// Set the value and its HTML rep
|
||||
var old_value = this.Value;
|
||||
this.Value = value;
|
||||
this.TextNode.innerHTML = value;
|
||||
|
||||
// Call change handler
|
||||
if (this.OnChange)
|
||||
this.OnChange(value, old_value);
|
||||
}
|
||||
|
||||
|
||||
ComboBox.prototype.GetValue = function()
|
||||
{
|
||||
return this.Value;
|
||||
}
|
||||
|
||||
|
||||
function OnMouseDown(self, evt)
|
||||
{
|
||||
// If this check isn't made, the click will trigger from the popup, too
|
||||
var node = DOM.Event.GetNode(evt);
|
||||
if (DOM.Node.Contains(node, self.Node))
|
||||
{
|
||||
// Add the depression class and activate release handlers
|
||||
DOM.Node.AddClass(self.Node, "ComboBoxPressed");
|
||||
DOM.Event.AddHandler(self.Node, "mouseout", self.OnMouseOutDelegate);
|
||||
DOM.Event.AddHandler(self.Node, "mouseup", self.OnMouseUpDelegate);
|
||||
|
||||
DOM.Event.StopAll(evt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnMouseUp(self, confirm, evt)
|
||||
{
|
||||
// Remove depression class and remove release handlers
|
||||
DOM.Node.RemoveClass(self.Node, "ComboBoxPressed");
|
||||
DOM.Event.RemoveHandler(self.Node, "mouseout", self.OnMouseOutDelegate);
|
||||
DOM.Event.RemoveHandler(self.Node, "mouseup", self.OnMouseUpDelegate);
|
||||
|
||||
// If this is a confirmed press and there are some values in the list, show the popup
|
||||
if (confirm && self.Values.length > 0)
|
||||
{
|
||||
var selection_index = self.Values.indexOf(self.Value);
|
||||
self.Popup.Show(selection_index);
|
||||
}
|
||||
|
||||
DOM.Event.StopAll(evt);
|
||||
}
|
||||
|
||||
|
||||
return ComboBox;
|
||||
})();
|
|
@ -1,34 +0,0 @@
|
|||
|
||||
namespace("WM");
|
||||
|
||||
|
||||
WM.Container = (function()
|
||||
{
|
||||
var template_html = "<div class='Container'></div>";
|
||||
|
||||
|
||||
function Container(x, y, w, h)
|
||||
{
|
||||
// Create a simple container node
|
||||
this.Node = DOM.Node.CreateHTML(template_html);
|
||||
this.SetPosition(x, y);
|
||||
this.SetSize(w, h);
|
||||
}
|
||||
|
||||
|
||||
Container.prototype.SetPosition = function(x, y)
|
||||
{
|
||||
this.Position = [ x, y ];
|
||||
DOM.Node.SetPosition(this.Node, this.Position);
|
||||
}
|
||||
|
||||
|
||||
Container.prototype.SetSize = function(w, h)
|
||||
{
|
||||
this.Size = [ w, h ];
|
||||
DOM.Node.SetSize(this.Node, this.Size);
|
||||
}
|
||||
|
||||
|
||||
return Container;
|
||||
})();
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue