Compare commits
18 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52806e4457 | ||
|
|
a45ae025d6 | ||
|
|
742ef283e4 | ||
|
|
321c677da2 | ||
|
|
6331a2bf79 | ||
|
|
c37be6798f | ||
|
|
9870dc128e | ||
| 58ffcff459 | |||
| 6d98c22890 | |||
| 2e24fef5ac | |||
| f252823b4b | |||
| 4497cc7f68 | |||
| 2756d676f1 | |||
| f622a55c5c | |||
| 1eeb328b60 | |||
| 68ae1fb265 | |||
|
|
8c1324c074 | ||
|
|
3aea707982 |
59 changed files with 22415 additions and 144 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -8,3 +8,9 @@ raytracer
|
||||||
|
|
||||||
# Actual output image
|
# Actual output image
|
||||||
image.ppm
|
image.ppm
|
||||||
|
|
||||||
|
# Profiler data
|
||||||
|
perf.*
|
||||||
|
|
||||||
|
# Core dumps
|
||||||
|
core
|
||||||
|
|
|
||||||
14
Makefile
14
Makefile
|
|
@ -1,8 +1,14 @@
|
||||||
raytracer: main.cpp vec3.hpp color.hpp ray.hpp
|
INCLUDE=./include
|
||||||
@g++ -g -Wall -Wextra -Wpedantic main.cpp -o raytracer
|
LIBS=-pthread -lm
|
||||||
|
FLAGS=-Ofast -march=native -g -Wall -Wextra -Wpedantic
|
||||||
|
|
||||||
|
raytracer: camera.hpp color.hpp hittable.hpp hittable_list.hpp main.cpp material.hpp random.h ray.hpp rtweekend.hpp sphere.hpp vec3.hpp $(INCLUDE)/Remotery.c $(INCLUDE)/Remotery.h
|
||||||
|
g++ $(FLAGS) -I$(INCLUDE) $(LIBS) main.cpp -o raytracer
|
||||||
|
|
||||||
|
make debug:
|
||||||
|
|
||||||
image: raytracer
|
image: raytracer
|
||||||
@./raytracer > image.ppm
|
@./raytracer -o image.ppm
|
||||||
@if [[ $$TERM = "xterm-kitty" ]]; then\
|
@if [ $$TERM = "xterm-kitty" ]; then\
|
||||||
kitty icat image.ppm;\
|
kitty icat image.ppm;\
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
52
camera.hpp
Normal file
52
camera.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#ifndef CAMERA_H
|
||||||
|
#define CAMERA_H
|
||||||
|
|
||||||
|
#include "rtweekend.hpp"
|
||||||
|
|
||||||
|
struct camera {
|
||||||
|
/* Attributes */
|
||||||
|
point3 origin;
|
||||||
|
point3 lower_left_corner;
|
||||||
|
vec3 horizontal;
|
||||||
|
vec3 vertical;
|
||||||
|
vec3 u,v,w;
|
||||||
|
float lens_radius;
|
||||||
|
|
||||||
|
/* Constructors */
|
||||||
|
camera(point3 lookfrom,
|
||||||
|
point3 lookat,
|
||||||
|
vec3 vup,
|
||||||
|
float vfov,
|
||||||
|
float aspect_ratio,
|
||||||
|
float aperture,
|
||||||
|
float focus_dist)
|
||||||
|
{
|
||||||
|
float theta = degrees_to_radians(vfov);
|
||||||
|
float h = tan(theta/2);
|
||||||
|
float viewport_height = 2.0 * h;
|
||||||
|
float viewport_width = aspect_ratio * viewport_height;
|
||||||
|
|
||||||
|
w = normalize(lookfrom - lookat);
|
||||||
|
u = normalize(cross(vup,w));
|
||||||
|
v = cross(w, u);
|
||||||
|
|
||||||
|
origin = lookfrom;
|
||||||
|
horizontal = focus_dist * viewport_width * u;
|
||||||
|
vertical = focus_dist * viewport_height * v;
|
||||||
|
lower_left_corner = origin - horizontal/2 - vertical/2 - focus_dist*w;
|
||||||
|
|
||||||
|
lens_radius = aperture/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Methods */
|
||||||
|
|
||||||
|
ray get_ray(float s, float t, int32_t thread_id = 0) const
|
||||||
|
{
|
||||||
|
vec3 rd = lens_radius * random_in_unit_disk(thread_id);
|
||||||
|
vec3 offset = u * rd.x + v * rd.y;
|
||||||
|
|
||||||
|
return ray(origin + offset, lower_left_corner + s*horizontal + t*vertical - origin - offset);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
31
color.hpp
31
color.hpp
|
|
@ -1,18 +1,35 @@
|
||||||
#ifndef COLOR_H
|
#ifndef COLOR_H
|
||||||
#define COLOR_H
|
#define COLOR_H
|
||||||
|
|
||||||
#include "vec3.hpp"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "rtweekend.hpp"
|
||||||
|
|
||||||
/* Writes color components as a space-delimited string of numbers in the range [0,255] */
|
/* Writes color components as a space-delimited string of numbers in the range [0,255] */
|
||||||
void write_color(FILE *fp, color c)
|
void write_color(FILE *fp, color c, uint32_t samples_per_pixel)
|
||||||
{
|
{
|
||||||
fprintf(fp, "%d %d %d\n",
|
float scale = 1.0 / samples_per_pixel;
|
||||||
(uint8_t) (255 * c.x),
|
|
||||||
(uint8_t) (255 * c.y),
|
// Divide the color by the number of samples
|
||||||
(uint8_t) (255 * c.z));
|
float r = sqrt(c.x * scale);
|
||||||
|
float g = sqrt(c.y * scale);
|
||||||
|
float b = sqrt(c.z * scale);
|
||||||
|
|
||||||
|
/* Write output */
|
||||||
|
fprintf(fp,
|
||||||
|
"%d %d %d\n",
|
||||||
|
(uint8_t) (255 * clamp(r, 0, 1)),
|
||||||
|
(uint8_t) (255 * clamp(g, 0, 1)),
|
||||||
|
(uint8_t) (255 * clamp(b, 0, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_image(color *image, uint64_t n, FILE *fp, uint32_t samples_per_pixel)
|
||||||
|
{
|
||||||
|
for (int64_t i = n-1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
write_color(fp, image[i], samples_per_pixel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
12
hittable.hpp
Normal file
12
hittable.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef HITTABLE_H
|
||||||
|
#define HITTABLE_H
|
||||||
|
|
||||||
|
#include "rtweekend.hpp"
|
||||||
|
#include "material.hpp"
|
||||||
|
|
||||||
|
/* Virtual class that represents objects who could collide against a ray */
|
||||||
|
struct hittable {
|
||||||
|
virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
52
hittable_list.hpp
Normal file
52
hittable_list.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#ifndef HITTABLE_LIST_H
|
||||||
|
#define HITTABLE_LIST_H
|
||||||
|
|
||||||
|
#include "hittable.hpp"
|
||||||
|
#include "sphere.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using std::shared_ptr;
|
||||||
|
using std::make_shared;
|
||||||
|
|
||||||
|
template <typename T = sphere>
|
||||||
|
struct hittable_list {
|
||||||
|
/* Attributes */
|
||||||
|
std::vector<T> objects;
|
||||||
|
|
||||||
|
/* Constructors */
|
||||||
|
hittable_list () {}
|
||||||
|
hittable_list(T object) { add(object); }
|
||||||
|
|
||||||
|
/* Methods */
|
||||||
|
void clear() { objects.clear(); }
|
||||||
|
void add (T h) { objects.push_back(h); }
|
||||||
|
|
||||||
|
bool hit(const ray& r, float t_min, float t_max, hit_record& rec);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool hittable_list<T>::hit(const ray& r, float t_min, float t_max, hit_record& rec)
|
||||||
|
{
|
||||||
|
rmt_ScopedCPUSample(HittableList_Hit, RMTSF_Aggregate);
|
||||||
|
hit_record temp_rec;
|
||||||
|
bool hit_anything = false;
|
||||||
|
float closest_so_far = t_max;
|
||||||
|
|
||||||
|
uint32_t s = objects.size();
|
||||||
|
for (uint32_t i = 0; i < s; ++i)
|
||||||
|
{
|
||||||
|
T *object = &objects[i];
|
||||||
|
if (object->hit(r, t_min, closest_so_far, temp_rec))
|
||||||
|
{
|
||||||
|
hit_anything = true;
|
||||||
|
closest_so_far = temp_rec.t;
|
||||||
|
rec = temp_rec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hit_anything;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
8809
include/Remotery.c
Normal file
8809
include/Remotery.c
Normal file
File diff suppressed because it is too large
Load diff
679
include/Remotery.h
Normal file
679
include/Remotery.h
Normal file
|
|
@ -0,0 +1,679 @@
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
// Architecture identification
|
||||||
|
#ifdef RMT_PLATFORM_WINDOWS
|
||||||
|
#ifdef _M_AMD64
|
||||||
|
#define RMT_ARCH_64BIT
|
||||||
|
#else
|
||||||
|
#define RMT_ARCH_32BIT
|
||||||
|
#endif
|
||||||
|
#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
|
||||||
|
// clang-format off
|
||||||
|
typedef enum rmtError
|
||||||
|
{
|
||||||
|
RMT_ERROR_NONE,
|
||||||
|
RMT_ERROR_RECURSIVE_SAMPLE, // Not an error but an internal message to calling code
|
||||||
|
RMT_ERROR_UNKNOWN, // An error with a message yet to be defined, only for internal error handling
|
||||||
|
|
||||||
|
// 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
|
||||||
|
RMT_ERROR_OPEN_THREAD_HANDLE_FAIL, // Failed to open a thread handle, given a thread id
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
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,
|
||||||
|
|
||||||
|
// Set this flag on any of your root samples so that Remotery will assert if it ends up *not* being the root sample.
|
||||||
|
// This will quickly allow you to detect Begin/End mismatches causing a sample tree imbalance.
|
||||||
|
RMTSF_Root = 4,
|
||||||
|
} 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;
|
||||||
|
|
||||||
|
// Whether to enable runtime thread sampling that discovers which processors a thread is running
|
||||||
|
// on. This will suspend and resume threads from outside repeatdly and inject code into each
|
||||||
|
// thread that automatically instruments the processor.
|
||||||
|
// Default: Enabled
|
||||||
|
rmtBool enableThreadSampler;
|
||||||
|
|
||||||
|
// 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 logPath;
|
||||||
|
} 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
|
||||||
59
include/RemoteryMetal.mm
Normal file
59
include/RemoteryMetal.mm
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
//
|
||||||
|
// 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; }];
|
||||||
|
}
|
||||||
4649
include/indicators.hpp
Normal file
4649
include/indicators.hpp
Normal file
File diff suppressed because it is too large
Load diff
461
main.cpp
461
main.cpp
|
|
@ -1,73 +1,448 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#include "vec3.hpp"
|
#define RMT_ENABLED 0
|
||||||
|
|
||||||
|
// Lib includes
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||||
|
#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||||
|
#include <Remotery.c>
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
#include <indicators.hpp>
|
||||||
|
|
||||||
|
// Internal includes
|
||||||
|
#include "rtweekend.hpp"
|
||||||
#include "color.hpp"
|
#include "color.hpp"
|
||||||
#include "ray.hpp"
|
#include "hittable_list.hpp"
|
||||||
|
#include "sphere.hpp"
|
||||||
|
#include "camera.hpp"
|
||||||
|
|
||||||
color ray_color(const ray& r);
|
#ifdef DEBUG
|
||||||
double hit_sphere(const point3& center, double radius, const ray& r);
|
#define print_timers() print_timers_()
|
||||||
|
#else
|
||||||
|
#define print_timers()
|
||||||
|
#endif
|
||||||
|
|
||||||
color ray_color(const ray& r)
|
// Threading structs
|
||||||
|
struct thread_args
|
||||||
{
|
{
|
||||||
double t = hit_sphere(point3(0,0,-1), 0.5, r);
|
int32_t thread_id;
|
||||||
if (t > 0.0)
|
int32_t start;
|
||||||
{
|
int32_t end;
|
||||||
vec3 N = normalize(r.at(t) - vec3(0,0,-1));
|
};
|
||||||
return 0.5*color(N.x+1, N.y+1, N.z+1);
|
|
||||||
}
|
|
||||||
vec3 unit_direction = normalize(r.direction);
|
|
||||||
t = 0.5*(unit_direction.y + 1.0);
|
|
||||||
|
|
||||||
return (1-t) * color(1,1,1) + t*color(0.5, 0.7, 1.0);
|
// Function signatures
|
||||||
|
|
||||||
|
color ray_color(const ray& r, const hittable& world, int32_t depth);
|
||||||
|
float hit_sphere(const point3& center, float radius, const ray& r);
|
||||||
|
void *raytrace_lines(void *arg);
|
||||||
|
hittable_list<sphere> random_scene();
|
||||||
|
|
||||||
|
// Global vars
|
||||||
|
indicators::DynamicProgress<indicators::BlockProgressBar> progress_bars;
|
||||||
|
const char *default_file = "image.ppm";
|
||||||
|
FILE *output_file_handle;
|
||||||
|
|
||||||
|
// Image
|
||||||
|
float aspect_ratio;
|
||||||
|
int32_t image_width;
|
||||||
|
int32_t image_height;
|
||||||
|
int32_t samples_per_pixel;
|
||||||
|
int32_t max_depth;
|
||||||
|
|
||||||
|
color *image;
|
||||||
|
uint64_t bytes_per_line;
|
||||||
|
uint64_t bytes_per_pixel;
|
||||||
|
|
||||||
|
// World
|
||||||
|
hittable_list<sphere> world;
|
||||||
|
camera *global_camera;
|
||||||
|
|
||||||
|
hittable_list<sphere> random_scene() {
|
||||||
|
hittable_list<sphere> world;
|
||||||
|
|
||||||
|
auto ground_material = make_shared<lambertian>(color(0.5, 0.5, 0.5));
|
||||||
|
world.add(sphere(point3(0,-1000,0), 1000, ground_material));
|
||||||
|
|
||||||
|
for (int32_t a = -11; a < 11; a++)
|
||||||
|
{
|
||||||
|
for (int32_t b = -11; b < 11; b++)
|
||||||
|
{
|
||||||
|
float choose_mat = random_float();
|
||||||
|
point3 center(a + 0.9*random_float(), 0.2, b + 0.9*random_float());
|
||||||
|
|
||||||
|
if ((center - point3(4, 0.2, 0)).length() > 0.9)
|
||||||
|
{
|
||||||
|
shared_ptr<material> sphere_material;
|
||||||
|
if (choose_mat < 0.8)
|
||||||
|
{
|
||||||
|
// diffuse
|
||||||
|
color albedo = color::random() * color::random();
|
||||||
|
sphere_material = make_shared<lambertian>(albedo);
|
||||||
|
world.add(sphere(center, 0.2, sphere_material));
|
||||||
|
}
|
||||||
|
else if (choose_mat < 0.95)
|
||||||
|
{
|
||||||
|
// metal
|
||||||
|
color albedo = color::random(0.5, 1);
|
||||||
|
float fuzz = random_float(0, 0.5);
|
||||||
|
sphere_material = make_shared<metal>(albedo, fuzz);
|
||||||
|
world.add(sphere(center, 0.2, sphere_material));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// glass
|
||||||
|
sphere_material = make_shared<dielectric>(1.5);
|
||||||
|
world.add(sphere(center, 0.2, sphere_material));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto material1 = make_shared<dielectric>(1.5);
|
||||||
|
world.add(sphere(point3(0, 1, 0), 1.0, material1));
|
||||||
|
|
||||||
|
auto material2 = make_shared<lambertian>(color(0.4, 0.2, 0.1));
|
||||||
|
world.add(sphere(point3(-4, 1, 0), 1.0, material2));
|
||||||
|
|
||||||
|
auto material3 = make_shared<metal>(color(0.7, 0.6, 0.5), 0.0);
|
||||||
|
world.add(sphere(point3(4, 1, 0), 1.0, material3));
|
||||||
|
|
||||||
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
double hit_sphere(const point3& center, double radius, const ray& r)
|
template<typename T>
|
||||||
|
color ray_color(const ray& r, hittable_list<T>& world, int32_t depth, int32_t thread_id)
|
||||||
|
{
|
||||||
|
rmt_ScopedCPUSample(Scatter, RMTSF_Aggregate | RMTSF_Recursive);
|
||||||
|
if (depth <= 0)
|
||||||
|
{
|
||||||
|
return color(0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
hit_record rec;
|
||||||
|
if (world.hit(r, 0.001, INFINITY, rec))
|
||||||
|
{
|
||||||
|
ray scattered;
|
||||||
|
color attenuation;
|
||||||
|
rmt_BeginCPUSample(Scatter, RMTSF_Aggregate);
|
||||||
|
bool visible = rec.mat_ptr->scatter(r, rec, attenuation, scattered, thread_id);
|
||||||
|
rmt_EndCPUSample();
|
||||||
|
if (visible)
|
||||||
|
{
|
||||||
|
return attenuation * ray_color(scattered, world, depth-1, thread_id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return color(0,0,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vec3 unit_direction = normalize(r.direction);
|
||||||
|
float t = 0.5 * (unit_direction.y + 1.0);
|
||||||
|
return (1-t) * color(1,1,1) + t*color(0.5,0.7,1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float hit_sphere(const point3& center, float radius, const ray& r)
|
||||||
{
|
{
|
||||||
vec3 oc = r.origin - center;
|
vec3 oc = r.origin - center;
|
||||||
double a = dot(r.direction, r.direction);
|
float a = r.direction.length_squared();
|
||||||
double b = 2.0 * dot(oc, r.direction);
|
float half_b = dot(oc, r.direction);
|
||||||
double c = dot(oc,oc) - radius*radius;
|
float c = oc.length_squared() - radius*radius;
|
||||||
double discriminant = b*b - 4*a*c;
|
float discriminant = half_b*half_b - a*c;
|
||||||
|
|
||||||
if (discriminant < 0)
|
if (discriminant < 0)
|
||||||
return -1;
|
return -1;
|
||||||
else
|
else
|
||||||
return (-b - sqrt(discriminant)) / (2.0*a);
|
return (-half_b - sqrt(discriminant)) / a;
|
||||||
}
|
}
|
||||||
|
int32_t main(int argc, char *argv[])
|
||||||
int main()
|
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Argument parsing */
|
||||||
|
int32_t c;
|
||||||
|
bool using_default_output = true;
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
static struct option long_options[] =
|
||||||
|
{
|
||||||
|
{"output", required_argument, 0, 'o'},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
/* getopt_long stores the option index here. */
|
||||||
|
int option_index = 0;
|
||||||
|
|
||||||
|
c = getopt_long (argc, argv, "o:",
|
||||||
|
long_options, &option_index);
|
||||||
|
|
||||||
|
/* Detect the end of the options. */
|
||||||
|
if (c == -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
/* If this option set a flag, do nothing else now. */
|
||||||
|
if (long_options[option_index].flag != 0)
|
||||||
|
break;
|
||||||
|
printf ("option %s", long_options[option_index].name);
|
||||||
|
if (optarg)
|
||||||
|
printf (" with arg %s", optarg);
|
||||||
|
printf ("\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
using_default_output = false;
|
||||||
|
output_file_handle = fopen(optarg, "w");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '?':
|
||||||
|
/* getopt_long already printed an error message. */
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (using_default_output)
|
||||||
|
{
|
||||||
|
output_file_handle = fopen(default_file, "w");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Profiling library initialization */
|
||||||
|
Remotery *rmt;
|
||||||
|
if (RMT_ERROR_NONE != rmt_CreateGlobalInstance(&rmt))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error starting Remotery\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
//indicators::show_console_cursor(false);
|
||||||
|
|
||||||
|
|
||||||
|
// Get the number of logical CPUs
|
||||||
|
int32_t ncores = sysconf(_SC_NPROCESSORS_ONLN);
|
||||||
|
// Initialize and seed the random number generators
|
||||||
|
pcg_table = (pcg32_random_t *) malloc(sizeof(pcg32_random_t) * ncores);
|
||||||
|
for (int32_t i = 0; i < ncores; ++i)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
if (timespec_get(&ts, TIME_UTC))
|
||||||
|
{
|
||||||
|
// Use higher quality seed
|
||||||
|
uint64_t seed = (uint64_t)(ts.tv_nsec ^ ts.tv_sec);
|
||||||
|
pcg_table[i] = { seed, seed };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Error, use default seed
|
||||||
|
pcg_table[i] = default_pcg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
double aspect_ratio = 16.0 / 9;
|
aspect_ratio = 3.0 / 2.0;
|
||||||
const int32_t image_width = 400;
|
image_width = 1200;
|
||||||
const int32_t image_height = (int32_t) (image_width / aspect_ratio);
|
image_height = (int32_t) (image_width / aspect_ratio);
|
||||||
|
samples_per_pixel = 500;
|
||||||
|
max_depth = 50;
|
||||||
|
|
||||||
|
image = (color*) malloc(image_width * image_height * sizeof(color));
|
||||||
|
bytes_per_line = sizeof(color) * image_width;
|
||||||
|
bytes_per_pixel = sizeof(color);
|
||||||
|
|
||||||
|
if (getenv("SPP"))
|
||||||
|
{
|
||||||
|
samples_per_pixel = strtol(getenv("SPP"), NULL, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// World
|
||||||
|
world = random_scene();
|
||||||
|
|
||||||
// Camera
|
// Camera
|
||||||
double viewport_height = 2.0;
|
point3 lookfrom(13,2,3);
|
||||||
double viewport_width = aspect_ratio * viewport_height;
|
point3 lookat(0,0,0);
|
||||||
double focal_length = 1.0;
|
vec3 vup(0,1,0);
|
||||||
|
float dist_to_focus = 10.0;
|
||||||
|
float aperture = 0.1;
|
||||||
|
|
||||||
point3 origin = point3(0,0,0);
|
camera cam = camera(lookfrom, lookat, vup, 20, aspect_ratio, aperture, dist_to_focus);
|
||||||
vec3 horizontal = vec3(viewport_width, 0, 0);
|
global_camera = &cam;
|
||||||
vec3 vertical = vec3(0, viewport_height, 0);
|
|
||||||
vec3 lower_left_corner = origin - horizontal/2 - vertical/2 - vec3(0, 0, focal_length);
|
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
printf("P3\n%d %d\n255\n", image_width, image_height);
|
fprintf(output_file_handle, "P3\n%d %d\n255\n", image_width, image_height);
|
||||||
|
|
||||||
for (int32_t j = image_height - 1; j >= 0; --j)
|
|
||||||
|
std::vector<pthread_t> threads;
|
||||||
|
std::vector<thread_args> args;
|
||||||
|
threads.reserve(ncores);
|
||||||
|
args.reserve(ncores);
|
||||||
|
|
||||||
|
std::vector<indicators::BlockProgressBar*> bar_memory;
|
||||||
|
bar_memory.reserve(ncores);
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < ncores; ++i)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "\rScanlines remaining: %d ", j);
|
|
||||||
fflush(stderr);
|
bar_memory[i] = new indicators::BlockProgressBar{indicators::option::BarWidth{50},
|
||||||
|
indicators::option::ForegroundColor{indicators::Color::white},
|
||||||
|
indicators::option::ShowElapsedTime{true},
|
||||||
|
indicators::option::ShowRemainingTime{true},
|
||||||
|
indicators::option::PrefixText{"Thread #" + std::to_string(i)}
|
||||||
|
};
|
||||||
|
|
||||||
|
progress_bars.push_back(*bar_memory[i]);
|
||||||
|
|
||||||
|
int32_t start;
|
||||||
|
int32_t end;
|
||||||
|
|
||||||
|
// Divide work among cores
|
||||||
|
start = image_height/ncores * i;
|
||||||
|
end = image_height/ncores * (i+1);
|
||||||
|
|
||||||
|
// Make sure we complete the whole picture even if the work is not perfectly divisible
|
||||||
|
if (i == ncores)
|
||||||
|
end = image_height;
|
||||||
|
|
||||||
|
args[i].start = start;
|
||||||
|
args[i].end = end;
|
||||||
|
args[i].thread_id = i;
|
||||||
|
|
||||||
|
// TODO: Check for errors
|
||||||
|
pthread_create(&threads[i], NULL, raytrace_lines, &args[i]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < ncores; ++i)
|
||||||
|
{
|
||||||
|
switch (pthread_join(threads[i], NULL))
|
||||||
|
{
|
||||||
|
case EDEADLK:
|
||||||
|
fprintf(stderr, "A deadlock was detected (e.g., two threads tried to join with each other); or thread specifies the calling thread.\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EINVAL:
|
||||||
|
fprintf(stderr, "thread is not a joinable thread OR\n"
|
||||||
|
"Another thread is already waiting to join with this thread.\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESRCH:
|
||||||
|
fprintf(stderr, "No thread with the ID thread could be found.\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_image(image, image_width*image_height, output_file_handle, samples_per_pixel);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Obsolete non-threaded implementation */
|
||||||
|
|
||||||
|
// for (int32_t j = image_height - 1; j >= 0; --j)
|
||||||
|
// {
|
||||||
|
// rmt_ScopedCPUSample(OuterLoop, RMTSF_Aggregate);
|
||||||
|
// fprintf(stderr, "\rScanlines remaining: %d ", j);
|
||||||
|
// print_timers();
|
||||||
|
// fflush(stderr);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// for (int32_t i = 0; i < image_width; ++i)
|
||||||
|
// {
|
||||||
|
// rmt_ScopedCPUSample(InnerLoop, RMTSF_Aggregate);
|
||||||
|
// color pixel_color = color(0,0,0);
|
||||||
|
|
||||||
|
// for (int32_t s = 0; s < samples_per_pixel; ++s)
|
||||||
|
// {
|
||||||
|
// float u = ((i + random_float()) / (image_width-1));
|
||||||
|
// float v = ((j + random_float()) / (image_height-1));
|
||||||
|
// ray r = cam.get_ray(u,v);
|
||||||
|
// pixel_color += ray_color(r, world, max_depth);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// write_color(output_file_handle, pixel_color, samples_per_pixel);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fprintf(stderr, "\nDone\n");
|
||||||
|
rmt_DestroyGlobalInstance(rmt);
|
||||||
|
free(image);
|
||||||
|
fclose(output_file_handle);
|
||||||
|
//indicators::show_console_cursor(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *raytrace_lines(void *arg)
|
||||||
|
{
|
||||||
|
|
||||||
|
thread_args arguments = *((thread_args*)arg);
|
||||||
|
|
||||||
|
int32_t start = arguments.start;
|
||||||
|
int32_t end = arguments.end;
|
||||||
|
int32_t thread_id = arguments.thread_id;
|
||||||
|
|
||||||
|
for (int32_t j = end - 1; j >= start; --j)
|
||||||
|
{
|
||||||
|
|
||||||
|
int32_t lines_expected = end-start;
|
||||||
|
int32_t lines_completed = end-j;
|
||||||
|
progress_bars[thread_id].set_option(indicators::option::PostfixText{std::to_string(lines_completed) + "/" + std::to_string(lines_expected)});
|
||||||
|
|
||||||
|
progress_bars[thread_id].set_progress(((float)lines_completed/lines_expected)*100);
|
||||||
|
rmt_ScopedCPUSample(OuterLoop, RMTSF_Aggregate);
|
||||||
for (int32_t i = 0; i < image_width; ++i)
|
for (int32_t i = 0; i < image_width; ++i)
|
||||||
{
|
{
|
||||||
double u = (double)i / (image_width-1);
|
color pixel_color = color(0,0,0);
|
||||||
double v = (double)j / (image_height-1);
|
for (int32_t s = 0; s < samples_per_pixel; ++s)
|
||||||
ray r = ray(origin, lower_left_corner + u*horizontal + v*vertical - origin);
|
{
|
||||||
color pixel_color = ray_color(r);
|
float u = ((i + random_float(thread_id)) / (image_width-1));
|
||||||
|
float v = ((j + random_float(thread_id)) / (image_height-1));
|
||||||
write_color(stdout, pixel_color);
|
ray r = global_camera->get_ray(u,v, thread_id);
|
||||||
|
pixel_color += ray_color(r, world, max_depth, thread_id);
|
||||||
|
}
|
||||||
|
int32_t index = j * image_width + i;
|
||||||
|
image[index] = pixel_color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fprintf(stderr, "\nDone\n");
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
debug_record debug_record_array[__COUNTER__];
|
||||||
|
|
||||||
|
void print_timers_()
|
||||||
|
{
|
||||||
|
for (uint32_t i = 0;
|
||||||
|
i < sizeof(debug_record_array) / sizeof(debug_record_array[0]);
|
||||||
|
++i)
|
||||||
|
{
|
||||||
|
debug_record *record = &debug_record_array[i];
|
||||||
|
fprintf(stderr,
|
||||||
|
"%d: %s:%s:%d; "
|
||||||
|
"Cycles = %ld; "
|
||||||
|
"Hit count %ld; "
|
||||||
|
"Cycles/hit %f; "
|
||||||
|
"Time %f",
|
||||||
|
i, record->filename, record->function_name, record->line_number,
|
||||||
|
record->cycles,
|
||||||
|
record->hit_count,
|
||||||
|
(double)record->cycles / record->hit_count,
|
||||||
|
(double)record->cycles / CLOCKS_PER_SEC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
||||||
100
material.hpp
Normal file
100
material.hpp
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
#ifndef MATERIAL_H
|
||||||
|
#define MATERIAL_H
|
||||||
|
|
||||||
|
#include "rtweekend.hpp"
|
||||||
|
|
||||||
|
struct material {
|
||||||
|
virtual bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered, int32_t thread_id = 0) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct lambertian : material {
|
||||||
|
/* Attributes */
|
||||||
|
color albedo;
|
||||||
|
// Constructor
|
||||||
|
lambertian(const color& c) { albedo = c; }
|
||||||
|
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
|
virtual bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered, int32_t thread_id = 0) const override
|
||||||
|
{
|
||||||
|
vec3 scatter_direction = rec.normal + random_unit_vector(thread_id);
|
||||||
|
|
||||||
|
/* NOTE: it is possible that the random vector we generate is exactly opposite to the normal vector,
|
||||||
|
in which case it will sum to a near-zero scatter vector and generate degenerate results.
|
||||||
|
We check for near-zero vectors here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (scatter_direction.near_zero())
|
||||||
|
scatter_direction = rec.normal;
|
||||||
|
|
||||||
|
scattered = ray(rec.p, scatter_direction);
|
||||||
|
attenuation = albedo;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
};
|
||||||
|
|
||||||
|
struct metal : material {
|
||||||
|
/* Attributes */
|
||||||
|
color albedo;
|
||||||
|
float fuzz;
|
||||||
|
// Constructor
|
||||||
|
metal(const color& c, float f)
|
||||||
|
{
|
||||||
|
albedo = c;
|
||||||
|
fuzz = f;
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered, int32_t thread_id) const override
|
||||||
|
{
|
||||||
|
vec3 reflected = reflect(normalize(r_in.direction), rec.normal);
|
||||||
|
scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere(thread_id));
|
||||||
|
attenuation = albedo;
|
||||||
|
return (dot(scattered.direction, rec.normal) > 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dielectric : material
|
||||||
|
{
|
||||||
|
/* Attributes */
|
||||||
|
float ri; // refraction index
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
dielectric(float refraction_index) { ri = refraction_index; }
|
||||||
|
|
||||||
|
/* Methods */
|
||||||
|
|
||||||
|
// Schlick's approximation of reflectance
|
||||||
|
static float reflectance(float cosine, float ref_idx)
|
||||||
|
{
|
||||||
|
float r0 = (1-ref_idx) / (1+ref_idx);
|
||||||
|
r0 = r0*r0;
|
||||||
|
return r0 + (1-r0)*pow((1 - cosine), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Virtual methods */
|
||||||
|
virtual bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered, int32_t thread_id) const override
|
||||||
|
{
|
||||||
|
attenuation = color(1,1,1);
|
||||||
|
float refraction_ratio = rec.front_face ? (1.0/ri) : ri;
|
||||||
|
|
||||||
|
vec3 unit_direction = normalize(r_in.direction);
|
||||||
|
|
||||||
|
float cos_theta = fmin(dot(-unit_direction, rec.normal), 1);
|
||||||
|
float sin_theta = sqrt(1.0 - cos_theta*cos_theta);
|
||||||
|
|
||||||
|
bool cannot_refract = refraction_ratio * sin_theta > 1.0;
|
||||||
|
vec3 direction;
|
||||||
|
|
||||||
|
if (cannot_refract || reflectance(cos_theta, refraction_ratio) > random_float(thread_id))
|
||||||
|
direction = reflect(unit_direction, rec.normal);
|
||||||
|
else
|
||||||
|
direction = refract(unit_direction, rec.normal, refraction_ratio);
|
||||||
|
|
||||||
|
scattered = ray(rec.p, direction);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
15
random.h
Normal file
15
random.h
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org
|
||||||
|
// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website)
|
||||||
|
|
||||||
|
typedef struct { uint64_t state; uint64_t inc; } pcg32_random_t;
|
||||||
|
|
||||||
|
uint32_t pcg32_random_r(pcg32_random_t* rng)
|
||||||
|
{
|
||||||
|
uint64_t oldstate = rng->state;
|
||||||
|
// Advance internal state
|
||||||
|
rng->state = oldstate * 6364136223846793005ULL + (rng->inc|1);
|
||||||
|
// Calculate output function (XSH RR), uses old state for max ILP
|
||||||
|
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
|
||||||
|
uint32_t rot = oldstate >> 59u;
|
||||||
|
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
|
||||||
|
}
|
||||||
2
ray.hpp
2
ray.hpp
|
|
@ -24,7 +24,7 @@ struct ray {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns position after time t
|
// Returns position after time t
|
||||||
point3 at(double t) const
|
point3 at(float t) const
|
||||||
{
|
{
|
||||||
return origin + t*direction;
|
return origin + t*direction;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
70
rtweekend.hpp
Normal file
70
rtweekend.hpp
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#ifndef RTWEEKEND_H
|
||||||
|
#define RTWEEKEND_H
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "timer.hpp"
|
||||||
|
#include "random.h"
|
||||||
|
|
||||||
|
pcg32_random_t *pcg_table;
|
||||||
|
pcg32_random_t default_pcg = { 0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL };
|
||||||
|
|
||||||
|
/* Utility functions */
|
||||||
|
inline float degrees_to_radians(float d)
|
||||||
|
{
|
||||||
|
return d * M_PI / 180;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns a float in the range [0,1) */
|
||||||
|
inline float random_float_()
|
||||||
|
{
|
||||||
|
return rand() * (1.0 / RAND_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns a float in the range [0,1) */
|
||||||
|
inline float random_float(int32_t thread_id = 0)
|
||||||
|
{
|
||||||
|
return pcg32_random_r(&pcg_table[thread_id]) * (1.0 / UINT32_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns a float in the range [min,max) */
|
||||||
|
inline float random_float(float min, float max, int32_t thread_id = 0)
|
||||||
|
{
|
||||||
|
return min + (max-min) * random_float(thread_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clamps a value between [min,max] */
|
||||||
|
inline float clamp(float v, float min, float max)
|
||||||
|
{
|
||||||
|
return v < min ? min : v > max ? max : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Common internal headers */
|
||||||
|
|
||||||
|
#include "ray.hpp"
|
||||||
|
#include "vec3.hpp"
|
||||||
|
|
||||||
|
struct material;
|
||||||
|
|
||||||
|
/* Common data structures */
|
||||||
|
|
||||||
|
struct hit_record {
|
||||||
|
point3 p;
|
||||||
|
vec3 normal;
|
||||||
|
std::shared_ptr<material> mat_ptr;
|
||||||
|
float t;
|
||||||
|
bool front_face;
|
||||||
|
|
||||||
|
inline void set_face_normal(const ray& r, const vec3& outward_normal)
|
||||||
|
{
|
||||||
|
front_face = dot(r.direction, outward_normal) < 0;
|
||||||
|
normal = front_face ? outward_normal : -outward_normal;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
61
sphere.hpp
Normal file
61
sphere.hpp
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
#ifndef SPHERE_H
|
||||||
|
#define SPHERE_H
|
||||||
|
|
||||||
|
#include "hittable.hpp"
|
||||||
|
#include "vec3.hpp"
|
||||||
|
|
||||||
|
struct sphere : hittable {
|
||||||
|
/* Attributes */
|
||||||
|
point3 center;
|
||||||
|
float radius;
|
||||||
|
std::shared_ptr<material> mat_ptr;
|
||||||
|
|
||||||
|
/* Contructor */
|
||||||
|
sphere(point3 c, float r, std::shared_ptr<material> m)
|
||||||
|
{
|
||||||
|
center = c;
|
||||||
|
radius = r;
|
||||||
|
mat_ptr = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Virtual methods declaration */
|
||||||
|
bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Virtual method implementations */
|
||||||
|
bool sphere::hit(const ray& r, float t_min, float t_max, hit_record& rec) const
|
||||||
|
{
|
||||||
|
/* NOTE: This function is called too many times (and too fast) for it to be
|
||||||
|
profiled in a usual way using Remotery. */
|
||||||
|
|
||||||
|
TIMED_BLOCK();
|
||||||
|
|
||||||
|
vec3 oc = r.origin - center;
|
||||||
|
float a = r.direction.length_squared();
|
||||||
|
float half_b = dot(oc, r.direction);
|
||||||
|
float c = oc.length_squared() - radius*radius;
|
||||||
|
|
||||||
|
float discriminant = half_b*half_b - a*c;
|
||||||
|
if (discriminant < 0)
|
||||||
|
return false;
|
||||||
|
float sqrtd = sqrt(discriminant);
|
||||||
|
|
||||||
|
// Find the nearest root that lies in the acceptable range
|
||||||
|
float root = (-half_b - sqrtd) / a;
|
||||||
|
if (root < t_min || t_max < root)
|
||||||
|
{
|
||||||
|
root = (-half_b + sqrtd) / a;
|
||||||
|
if (root < t_min || t_max < root)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rec.t = root;
|
||||||
|
rec.p = r.at(rec.t);
|
||||||
|
vec3 outward_normal = (rec.p - center) / radius;
|
||||||
|
rec.set_face_normal(r, outward_normal);
|
||||||
|
rec.mat_ptr = mat_ptr;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
45
timer.hpp
Normal file
45
timer.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#include <x86intrin.h>
|
||||||
|
#include "rtweekend.hpp"
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#define TIMED_BLOCK__(number, ...) timed_block timed_block_##Number(__COUNTER__, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__)
|
||||||
|
#define TIMED_BLOCK_(number, ...) TIMED_BLOCK__(number, ##__VA_ARGS__)
|
||||||
|
#define TIMED_BLOCK(...) TIMED_BLOCK_(__LINE__, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define TIMED_BLOCK(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
struct debug_record
|
||||||
|
{
|
||||||
|
uint64_t cycles;
|
||||||
|
uint64_t hit_count;
|
||||||
|
const char *filename;
|
||||||
|
const char *function_name;
|
||||||
|
|
||||||
|
uint32_t line_number;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern debug_record debug_record_array[];
|
||||||
|
|
||||||
|
struct timed_block {
|
||||||
|
// TODO: Thread safety
|
||||||
|
debug_record *record;
|
||||||
|
|
||||||
|
timed_block(int counter, const char *filename, int line_number, const char *function_name, int hit_count = 1)
|
||||||
|
{
|
||||||
|
record = debug_record_array + counter;
|
||||||
|
record->filename = filename;
|
||||||
|
record->line_number = line_number;
|
||||||
|
record->function_name = function_name;
|
||||||
|
record->cycles -= __rdtsc();
|
||||||
|
record->hit_count += hit_count;
|
||||||
|
}
|
||||||
|
~timed_block()
|
||||||
|
{
|
||||||
|
record->cycles += __rdtsc();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
73
vec3.h
73
vec3.h
|
|
@ -1,73 +0,0 @@
|
||||||
#ifndef VEC3_H
|
|
||||||
#define VEC3_H
|
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
struct vec3 {
|
|
||||||
/* Members */
|
|
||||||
double x;
|
|
||||||
double y;
|
|
||||||
double z;
|
|
||||||
|
|
||||||
/* Constructors */
|
|
||||||
|
|
||||||
// Default
|
|
||||||
vec3() { x = 0; y = 0; z = 0; };
|
|
||||||
|
|
||||||
// Constructor proper. Values default to 0
|
|
||||||
vec3(double x = 0, double y = 0, double z = 0)
|
|
||||||
{
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Overriden operators */
|
|
||||||
|
|
||||||
// - operator. Not to be confused with substraction
|
|
||||||
vec3 operator-()
|
|
||||||
{
|
|
||||||
return vec3(-x, -y, -z);
|
|
||||||
}
|
|
||||||
|
|
||||||
// straightforward vector sum
|
|
||||||
vec3& operator+=(const vec3 &v)
|
|
||||||
{
|
|
||||||
this.x += v.x;
|
|
||||||
this.y += v.y;
|
|
||||||
this.z += v.z;
|
|
||||||
}
|
|
||||||
|
|
||||||
// scalar multiplication
|
|
||||||
vec3& operator*=(const double t)
|
|
||||||
{
|
|
||||||
x *= t;
|
|
||||||
y *= t;
|
|
||||||
z *= t;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// division by a scalar t
|
|
||||||
vec3& operator/=(const double t)
|
|
||||||
{
|
|
||||||
x /= t;
|
|
||||||
y /= t;
|
|
||||||
z /= t;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Methods */
|
|
||||||
|
|
||||||
double length()
|
|
||||||
{
|
|
||||||
std::sqrt(x*x + y*y + z*z);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Type aliases */
|
|
||||||
typedef point3 vec3;
|
|
||||||
typedef color vec3;
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
113
vec3.hpp
113
vec3.hpp
|
|
@ -1,17 +1,16 @@
|
||||||
#ifndef VEC3_H
|
#ifndef VEC3_H
|
||||||
#define VEC3_H
|
#define VEC3_H
|
||||||
|
|
||||||
#include <math.h>
|
#include "rtweekend.hpp"
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
struct vec3 {
|
struct vec3 {
|
||||||
/* Members */
|
/* Members */
|
||||||
double x;
|
float x;
|
||||||
double y;
|
float y;
|
||||||
double z;
|
float z;
|
||||||
|
|
||||||
// Constructor proper. Values default to 0
|
// Constructor proper. Values default to 0
|
||||||
vec3(double x = 0, double y = 0, double z = 0)
|
vec3(float x = 0, float y = 0, float z = 0)
|
||||||
{
|
{
|
||||||
this->x = x;
|
this->x = x;
|
||||||
this->y = y;
|
this->y = y;
|
||||||
|
|
@ -21,7 +20,7 @@ struct vec3 {
|
||||||
/* Overriden operators */
|
/* Overriden operators */
|
||||||
|
|
||||||
// - operator. Not to be confused with substraction
|
// - operator. Not to be confused with substraction
|
||||||
vec3 operator-()
|
vec3 operator-() const
|
||||||
{
|
{
|
||||||
return vec3(-x, -y, -z);
|
return vec3(-x, -y, -z);
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +35,7 @@ struct vec3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scalar multiplication
|
// Scalar multiplication
|
||||||
vec3& operator*=(const double t)
|
vec3& operator*=(const float t)
|
||||||
{
|
{
|
||||||
x *= t;
|
x *= t;
|
||||||
y *= t;
|
y *= t;
|
||||||
|
|
@ -45,7 +44,7 @@ struct vec3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Division by a scalar t
|
// Division by a scalar t
|
||||||
vec3& operator/=(const double t)
|
vec3& operator/=(const float t)
|
||||||
{
|
{
|
||||||
x /= t;
|
x /= t;
|
||||||
y /= t;
|
y /= t;
|
||||||
|
|
@ -55,10 +54,35 @@ struct vec3 {
|
||||||
|
|
||||||
/* Methods */
|
/* Methods */
|
||||||
|
|
||||||
double length() const
|
float length() const
|
||||||
{
|
{
|
||||||
return sqrt(x*x + y*y + z*z);
|
return sqrt(x*x + y*y + z*z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Length squared, useful for some calculations
|
||||||
|
float length_squared() const
|
||||||
|
{
|
||||||
|
return x*x + y*y + z*z;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a vec3 with random components in the range [0,1)
|
||||||
|
inline static vec3 random(int32_t thread_id = 0)
|
||||||
|
{
|
||||||
|
return vec3(random_float(thread_id), random_float(thread_id), random_float(thread_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a vec3 with random components in the range [min, max)
|
||||||
|
inline static vec3 random(float min, float max, int32_t thread_id = 0)
|
||||||
|
{
|
||||||
|
return vec3(random_float(min, max, thread_id), random_float(min, max, thread_id), random_float(min, max, thread_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all vector components are near zero
|
||||||
|
bool near_zero() const
|
||||||
|
{
|
||||||
|
float s = 1e-8;
|
||||||
|
return (fabs(x) < s) && (fabs(y) < s) && (fabs(z) < s);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Type aliases */
|
/* Type aliases */
|
||||||
|
|
@ -68,6 +92,11 @@ typedef vec3 color;
|
||||||
|
|
||||||
/* More overloads */
|
/* More overloads */
|
||||||
|
|
||||||
|
inline bool operator==(const vec3 &u, const vec3 &v)
|
||||||
|
{
|
||||||
|
return u.x == v.x && u.y == v.y && u.z == v.z;
|
||||||
|
}
|
||||||
|
|
||||||
// Straightforward vector sum
|
// Straightforward vector sum
|
||||||
inline vec3 operator+(const vec3 &u, const vec3 &v)
|
inline vec3 operator+(const vec3 &u, const vec3 &v)
|
||||||
{
|
{
|
||||||
|
|
@ -93,31 +122,33 @@ inline vec3 operator*(const vec3 &u, const vec3 &v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scalar product
|
// Scalar product
|
||||||
inline vec3 operator*(double t,const vec3 &v)
|
inline vec3 operator*(float t,const vec3 &v)
|
||||||
{
|
{
|
||||||
return vec3(t*v.x, t*v.y, t*v.z);
|
return vec3(t*v.x, t*v.y, t*v.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline vec3 operator*(const vec3 &v, double t)
|
inline vec3 operator*(const vec3 &v, float t)
|
||||||
{
|
{
|
||||||
return t * v;
|
return t * v;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vector division by scalar. Note that we redefine it as multiplying by 1/t to avoid division by 0
|
// Vector division by scalar. Note that we redefine it as multiplying by 1/t to avoid division by 0
|
||||||
inline vec3 operator/(vec3 v, double t)
|
inline vec3 operator/(vec3 v, float t)
|
||||||
{
|
{
|
||||||
return 1/t * v;
|
return 1/t * v;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Straightforward dot product
|
// Straightforward dot product
|
||||||
inline double dot(const vec3 &u, const vec3 &v)
|
inline float dot(const vec3 &u, const vec3 &v)
|
||||||
{
|
{
|
||||||
|
|
||||||
return u.x*v.x + u.y*v.y + u.z*v.z;
|
return u.x*v.x + u.y*v.y + u.z*v.z;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cross product between two vectors
|
// Cross product between two vectors
|
||||||
inline vec3 cross(const vec3 &u, const vec3 &v)
|
inline vec3 cross(const vec3 &u, const vec3 &v)
|
||||||
{
|
{
|
||||||
|
|
||||||
return vec3(u.y * v.z - u.z * v.y,
|
return vec3(u.y * v.z - u.z * v.y,
|
||||||
u.z * v.x - u.x * v.z,
|
u.z * v.x - u.x * v.z,
|
||||||
u.x * v.y - u.y * v.x);
|
u.x * v.y - u.y * v.x);
|
||||||
|
|
@ -126,7 +157,61 @@ inline vec3 cross(const vec3 &u, const vec3 &v)
|
||||||
// Normalize vector so its length = 1
|
// Normalize vector so its length = 1
|
||||||
inline vec3 normalize(const vec3 v)
|
inline vec3 normalize(const vec3 v)
|
||||||
{
|
{
|
||||||
|
|
||||||
return v / v.length();
|
return v / v.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a vec3 of random components between [-1,1) that is inside a unit sphere
|
||||||
|
vec3 random_in_unit_sphere(int32_t thread_id)
|
||||||
|
{
|
||||||
|
// Iterate until we find a vector with length < 1
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
vec3 p = vec3::random(-1,1, thread_id);
|
||||||
|
if (p.length_squared() >= 1)
|
||||||
|
continue;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a normalized version of the above vector
|
||||||
|
vec3 random_unit_vector(int32_t thread_id)
|
||||||
|
{
|
||||||
|
return normalize(random_in_unit_sphere(thread_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 random_in_hemisphere(const vec3& normal, int32_t thread_id)
|
||||||
|
{
|
||||||
|
vec3 in_unit_sphere = random_in_unit_sphere(thread_id);
|
||||||
|
|
||||||
|
if (dot(in_unit_sphere, normal) > 0.0)
|
||||||
|
return in_unit_sphere;
|
||||||
|
else
|
||||||
|
return -in_unit_sphere;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reflect like a metallic material
|
||||||
|
vec3 reflect(const vec3& v, const vec3 n)
|
||||||
|
{
|
||||||
|
return v - 2*dot(v,n)*n;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 refract(const vec3& uv, const vec3& n, float etai_over_etat)
|
||||||
|
{
|
||||||
|
float cos_theta = fmin(dot(-uv, n), 1.0);
|
||||||
|
vec3 r_out_perp = etai_over_etat * (uv + cos_theta*n);
|
||||||
|
vec3 r_out_parallel = -sqrt(fabs(1.0 - r_out_perp.length_squared())) * n;
|
||||||
|
return r_out_perp + r_out_parallel;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 random_in_unit_disk(int32_t thread_id)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto p = vec3(random_float(-1,1,thread_id), random_float(-1,1,thread_id), 0);
|
||||||
|
if (p.length_squared() >= 1) continue;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
218
vis/Code/Console.js
Normal file
218
vis/Code/Console.js
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
|
||||||
|
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 = 10000;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Console.prototype.TriggerUpdate = function()
|
||||||
|
{
|
||||||
|
this.AppTextUpdatePending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnLog(self, socket, data_view_reader)
|
||||||
|
{
|
||||||
|
var text = data_view_reader.GetString();
|
||||||
|
self.AppTextBuffer = LogText(self.AppTextBuffer, text);
|
||||||
|
|
||||||
|
// Don't register text as updating if disconnected as this implies a trace is being loaded, which we want to speed up
|
||||||
|
if (self.Server.Connected())
|
||||||
|
{
|
||||||
|
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 = 100 * 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;
|
||||||
|
})();
|
||||||
52
vis/Code/DataViewReader.js
Normal file
52
vis/Code/DataViewReader.js
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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.AtEnd = function()
|
||||||
|
{
|
||||||
|
return this.Offset >= this.DataView.byteLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
})();
|
||||||
53
vis/Code/NameMap.js
Normal file
53
vis/Code/NameMap.js
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
class NameMap
|
||||||
|
{
|
||||||
|
constructor(text_buffer)
|
||||||
|
{
|
||||||
|
this.names = { };
|
||||||
|
this.textBuffer = text_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Get(name_hash)
|
||||||
|
{
|
||||||
|
// Return immediately if it's in the hash
|
||||||
|
let name = this.names[name_hash];
|
||||||
|
if (name != undefined)
|
||||||
|
{
|
||||||
|
return [ true, name ];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temporary name that uses the hash
|
||||||
|
name = {
|
||||||
|
string: name_hash.toString(),
|
||||||
|
hash: name_hash
|
||||||
|
};
|
||||||
|
this.names[name_hash] = name;
|
||||||
|
|
||||||
|
// Add to the text buffer the first time this name is encountered
|
||||||
|
name.textEntry = this.textBuffer.AddText(name.string);
|
||||||
|
|
||||||
|
return [ false, name ];
|
||||||
|
}
|
||||||
|
|
||||||
|
Set(name_hash, name_string)
|
||||||
|
{
|
||||||
|
// Create the name on-demand if its hash doesn't exist
|
||||||
|
let name = this.names[name_hash];
|
||||||
|
if (name == undefined)
|
||||||
|
{
|
||||||
|
name = {
|
||||||
|
string: name_string,
|
||||||
|
hash: name_hash
|
||||||
|
};
|
||||||
|
this.names[name_hash] = name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
name.string = name_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the updated text to the buffer
|
||||||
|
name.textEntry = this.textBuffer.AddText(name_string);
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
vis/Code/PixelTimeRange.js
Normal file
61
vis/Code/PixelTimeRange.js
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
class PixelTimeRange
|
||||||
|
{
|
||||||
|
constructor(start_us, span_us, span_px)
|
||||||
|
{
|
||||||
|
this.Span_px = span_px;
|
||||||
|
this.Set(start_us, span_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetStart(start_us)
|
||||||
|
{
|
||||||
|
this.Start_us = start_us;
|
||||||
|
this.End_us = start_us + this.Span_us;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetEnd(end_us)
|
||||||
|
{
|
||||||
|
this.End_us = end_us;
|
||||||
|
this.Start_us = end_us - this.Span_us;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetPixelSpan(span_px)
|
||||||
|
{
|
||||||
|
this.Span_px = span_px;
|
||||||
|
this.usPerPixel = this.Span_px / this.Span_us;
|
||||||
|
}
|
||||||
|
|
||||||
|
PixelOffset(time_us)
|
||||||
|
{
|
||||||
|
return Math.floor((time_us - this.Start_us) * this.usPerPixel);
|
||||||
|
}
|
||||||
|
|
||||||
|
PixelSize(time_us)
|
||||||
|
{
|
||||||
|
return Math.floor(time_us * this.usPerPixel);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeAtPosition(position)
|
||||||
|
{
|
||||||
|
return this.Start_us + position / this.usPerPixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
Clone()
|
||||||
|
{
|
||||||
|
return new PixelTimeRange(this.Start_us, this.Span_us, this.Span_px);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetAsUniform(gl, program)
|
||||||
|
{
|
||||||
|
glSetUniform(gl, program, "inTimeRange.usStart", this.Start_us);
|
||||||
|
glSetUniform(gl, program, "inTimeRange.usPerPixel", this.usPerPixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
540
vis/Code/Remotery.js
Normal file
540
vis/Code/Remotery.js
Normal file
|
|
@ -0,0 +1,540 @@
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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;
|
||||||
|
this.SyncTimelines = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
this.Server.AddDisconnectHandler(Bind(OnDisconnect, 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.SampleTimelineWindow = new TimelineWindow(this.WindowManager, "Sample Timeline", this.Settings, Bind(OnTimelineCheck, this));
|
||||||
|
this.SampleTimelineWindow.SetOnHover(Bind(OnSampleHover, this));
|
||||||
|
this.SampleTimelineWindow.SetOnSelected(Bind(OnSampleSelected, this));
|
||||||
|
this.ProcessorTimelineWindow = new TimelineWindow(this.WindowManager, "Processor Timeline", this.Settings, null);
|
||||||
|
|
||||||
|
this.SampleTimelineWindow.SetOnMoved(Bind(OnTimelineMoved, this));
|
||||||
|
this.ProcessorTimelineWindow.SetOnMoved(Bind(OnTimelineMoved, this));
|
||||||
|
|
||||||
|
this.TraceDrop = new TraceDrop(this);
|
||||||
|
|
||||||
|
this.NbSampleWindows = 0;
|
||||||
|
this.SampleWindows = { };
|
||||||
|
this.FrameHistory = { };
|
||||||
|
this.ProcessorFrameHistory = { };
|
||||||
|
this.SelectedFrames = { };
|
||||||
|
this.sampleNames = new NameMap(this.SampleTimelineWindow.textBuffer);
|
||||||
|
this.threadNames = new NameMap(this.ProcessorTimelineWindow.textBuffer);
|
||||||
|
|
||||||
|
this.Server.AddMessageHandler("SMPL", Bind(OnSamples, this));
|
||||||
|
this.Server.AddMessageHandler("SSMP", Bind(OnSampleName, this));
|
||||||
|
this.Server.AddMessageHandler("PRTH", Bind(OnProcessorThreads, this));
|
||||||
|
this.Server.AddMessageHandler("THRN", Bind(OnThreadNames, 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);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Remotery.prototype.Clear = function()
|
||||||
|
{
|
||||||
|
// Clear timelines
|
||||||
|
this.SampleTimelineWindow.Clear();
|
||||||
|
this.ProcessorTimelineWindow.Clear();
|
||||||
|
|
||||||
|
// Close and clear all sample windows
|
||||||
|
for (var i in this.SampleWindows)
|
||||||
|
{
|
||||||
|
var sample_window = this.SampleWindows[i];
|
||||||
|
sample_window.Close();
|
||||||
|
}
|
||||||
|
this.NbSampleWindows = 0;
|
||||||
|
this.SampleWindows = { };
|
||||||
|
|
||||||
|
// Clear runtime data
|
||||||
|
this.FrameHistory = { };
|
||||||
|
this.ProcessorFrameHistory = { };
|
||||||
|
this.SelectedFrames = { };
|
||||||
|
this.sampleNames = new NameMap(this.SampleTimelineWindow.textBuffer);
|
||||||
|
this.threadNames = new NameMap(this.ProcessorTimelineWindow.textBuffer);
|
||||||
|
|
||||||
|
// Resize everything to fit new layout
|
||||||
|
OnResizeWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
self.Clear();
|
||||||
|
|
||||||
|
// Ensure the viewer is ready for realtime updates
|
||||||
|
self.TitleWindow.Unpause();
|
||||||
|
}
|
||||||
|
|
||||||
|
function OnDisconnect(self)
|
||||||
|
{
|
||||||
|
// Pause so the user can inspect the trace
|
||||||
|
self.TitleWindow.Pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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.SampleTimelineWindow.DrawAllRows();
|
||||||
|
self.ProcessorTimelineWindow.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.SampleTimelineWindow.DrawAllRows();
|
||||||
|
self.ProcessorTimelineWindow.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();
|
||||||
|
let [ name_exists, name ] = self.sampleNames.Get(sample.name_hash);
|
||||||
|
sample.name = name;
|
||||||
|
|
||||||
|
// If the name doesn't exist in the map yet, request it from the server
|
||||||
|
if (!name_exists)
|
||||||
|
{
|
||||||
|
if (self.Server.Connected())
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
// TODO(don): Get the profiler to pass these directly instead of hex colour
|
||||||
|
const colour = parseInt(sample.colour.slice(1), 16);
|
||||||
|
const r = (colour >> 16) & 255;
|
||||||
|
const g = (colour >> 8) & 255;
|
||||||
|
const b = colour & 255;
|
||||||
|
sample.rgbColour = [ r, g, b ];
|
||||||
|
|
||||||
|
// 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
|
||||||
|
let message = { };
|
||||||
|
message.sample_tree_bytes = data_view_reader.GetUInt32();
|
||||||
|
message.thread_name = data_view_reader.GetString();
|
||||||
|
message.nb_samples = data_view_reader.GetUInt32();
|
||||||
|
message.sample_digest = data_view_reader.GetUInt32();
|
||||||
|
message.partial_tree = data_view_reader.GetUInt32();
|
||||||
|
|
||||||
|
// Read samples
|
||||||
|
message.samples = [];
|
||||||
|
message.samples.push(DecodeSample(self, data_view_reader));
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnSamples(self, socket, data_view_reader)
|
||||||
|
{
|
||||||
|
// Discard any new samples while paused and connected
|
||||||
|
// Otherwise this stops a paused Remotery from loading new samples from disk
|
||||||
|
if (self.Settings.IsPaused && self.Server.Connected())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Binary decode incoming sample data
|
||||||
|
var message = DecodeSamples(self, data_view_reader);
|
||||||
|
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];
|
||||||
|
if (frame_history.length > 0 && frame_history[frame_history.length - 1].PartialTree)
|
||||||
|
{
|
||||||
|
// Always overwrite partial trees with new information
|
||||||
|
frame_history[frame_history.length - 1] = thread_frame;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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.SampleTimelineWindow.Window, self.Console.Window);
|
||||||
|
self.NbSampleWindows++;
|
||||||
|
MoveSampleWindows(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set on the window and timeline if connected as this implies a trace is being loaded, which we want to speed up
|
||||||
|
if (self.Server.Connected())
|
||||||
|
{
|
||||||
|
self.SampleWindows[name].OnSamples(message.nb_samples, message.sample_digest, message.samples);
|
||||||
|
self.SampleTimelineWindow.OnSamples(name, frame_history);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnSampleName(self, socket, data_view_reader)
|
||||||
|
{
|
||||||
|
// Add any names sent by the server to the local map
|
||||||
|
let name_hash = data_view_reader.GetUInt32();
|
||||||
|
let name_string = data_view_reader.GetString();
|
||||||
|
self.sampleNames.Set(name_hash, name_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnProcessorThreads(self, socket, data_view_reader)
|
||||||
|
{
|
||||||
|
let nb_processors = data_view_reader.GetUInt32();
|
||||||
|
let message_index = data_view_reader.GetUInt64();
|
||||||
|
|
||||||
|
// Decode each processor
|
||||||
|
for (let i = 0; i < nb_processors; i++)
|
||||||
|
{
|
||||||
|
let thread_id = data_view_reader.GetUInt32();
|
||||||
|
let thread_name_hash = data_view_reader.GetUInt32();
|
||||||
|
let sample_time = data_view_reader.GetUInt64();
|
||||||
|
|
||||||
|
// Add frame history for this processor
|
||||||
|
let processor_name = "Processor " + i.toString();
|
||||||
|
if (!(processor_name in self.ProcessorFrameHistory))
|
||||||
|
{
|
||||||
|
self.ProcessorFrameHistory[processor_name] = [ ];
|
||||||
|
}
|
||||||
|
let frame_history = self.ProcessorFrameHistory[processor_name];
|
||||||
|
|
||||||
|
if (thread_id == 0xFFFFFFFF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to merge this frame's samples with the previous frame if the are the same thread
|
||||||
|
if (frame_history.length > 0)
|
||||||
|
{
|
||||||
|
let last_thread_frame = frame_history[frame_history.length - 1];
|
||||||
|
if (last_thread_frame.threadId == thread_id && last_thread_frame.messageIndex == message_index - 1)
|
||||||
|
{
|
||||||
|
// Update last frame message index so that the next frame can check for continuity
|
||||||
|
last_thread_frame.messageIndex = message_index;
|
||||||
|
|
||||||
|
// Sum time elapsed on the previous frame
|
||||||
|
let us_length = sample_time - last_thread_frame.usLastStart;
|
||||||
|
last_thread_frame.usLastStart = sample_time;
|
||||||
|
last_thread_frame.EndTime_us += us_length;
|
||||||
|
last_thread_frame.Samples[0].us_length += us_length;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the thread name
|
||||||
|
let [ _, thread_name ] = self.threadNames.Get(thread_name_hash);
|
||||||
|
|
||||||
|
// Make a pastel-y colour from the thread name hash
|
||||||
|
let hash = thread_name.hash;
|
||||||
|
let r = 127 + (hash & 255) / 2;
|
||||||
|
let g = 127 + ((hash >> 4) & 255) / 2;
|
||||||
|
let b = 127 + ((hash >> 8) & 255) / 2;
|
||||||
|
|
||||||
|
// We are co-opting the sample rendering functionality of the timeline window to display processor threads as
|
||||||
|
// thread samples. Fabricate a thread frame message, packing the processor info into one root sample.
|
||||||
|
// TODO(don): Abstract the timeline window for pure range display as this is quite inefficient.
|
||||||
|
let thread_message = {
|
||||||
|
nb_samples: 1,
|
||||||
|
sample_digest: 0,
|
||||||
|
samples : [
|
||||||
|
{
|
||||||
|
name_hash: thread_name_hash,
|
||||||
|
name: thread_name,
|
||||||
|
id: thread_id,
|
||||||
|
colour: "#FFFFFF",
|
||||||
|
us_start: sample_time,
|
||||||
|
us_length: 250,
|
||||||
|
rgbColour: [ r, g, b ],
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a thread frame and annotate with data required to merge processor samples
|
||||||
|
let thread_frame = new ThreadFrame(thread_message);
|
||||||
|
thread_frame.threadId = thread_id;
|
||||||
|
thread_frame.messageIndex = message_index;
|
||||||
|
thread_frame.usLastStart = sample_time;
|
||||||
|
frame_history.push(thread_frame);
|
||||||
|
|
||||||
|
if (self.Server.Connected())
|
||||||
|
{
|
||||||
|
self.ProcessorTimelineWindow.OnSamples(processor_name, frame_history);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnThreadNames(self, socket, data_view_reader)
|
||||||
|
{
|
||||||
|
let name_hash = data_view_reader.GetUInt32();
|
||||||
|
let name_length = data_view_reader.GetUInt32();
|
||||||
|
let name_string = data_view_reader.GetStringOfLength(name_length);
|
||||||
|
self.threadNames.Set(name_hash, name_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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.SampleTimelineWindow.Window, self.Console.Window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnSampleHover(self, thread_name, hover)
|
||||||
|
{
|
||||||
|
if (!self.Settings.IsPaused)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let window_thread_name in self.SampleWindows)
|
||||||
|
{
|
||||||
|
let sample_window = self.SampleWindows[window_thread_name];
|
||||||
|
|
||||||
|
if (window_thread_name == thread_name && hover != null)
|
||||||
|
{
|
||||||
|
// Populate with sample under hover
|
||||||
|
let frame = hover[0];
|
||||||
|
sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// When there's no hover, go back to the selected frame
|
||||||
|
if (self.SelectedFrames[window_thread_name])
|
||||||
|
{
|
||||||
|
const frame = self.SelectedFrames[window_thread_name];
|
||||||
|
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.SampleTimelineWindow.WindowResized(10, w / 2 - 5, self.TitleWindow.Window);
|
||||||
|
self.ProcessorTimelineWindow.WindowResized(w / 2 + 5, w / 2 - 5, self.TitleWindow.Window);
|
||||||
|
for (var i in self.SampleWindows)
|
||||||
|
{
|
||||||
|
self.SampleWindows[i].WindowResized(self.SampleTimelineWindow.Window, self.Console.Window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnTimelineMoved(self, timeline)
|
||||||
|
{
|
||||||
|
if (self.Settings.SyncTimelines)
|
||||||
|
{
|
||||||
|
let other_timeline = timeline == self.ProcessorTimelineWindow ? self.SampleTimelineWindow : self.ProcessorTimelineWindow;
|
||||||
|
other_timeline.SetTimeRange(timeline.TimeRange.Start_us, timeline.TimeRange.Span_us);
|
||||||
|
other_timeline.DrawAllRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return Remotery;
|
||||||
|
})();
|
||||||
221
vis/Code/SampleWindow.js
Normal file
221
vis/Code/SampleWindow.js
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
|
||||||
|
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.Close = function()
|
||||||
|
{
|
||||||
|
this.Window.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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.ShowNoAnim();
|
||||||
|
else
|
||||||
|
this.Window.HideNoAnim();
|
||||||
|
|
||||||
|
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;
|
||||||
|
})();
|
||||||
275
vis/Code/Shaders.js
Normal file
275
vis/Code/Shaders.js
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
const TimelineVShader =`#version 300 es
|
||||||
|
|
||||||
|
#define CANVAS_BORDER 1.0
|
||||||
|
#define SAMPLE_HEIGHT 16.0
|
||||||
|
#define SAMPLE_BORDER 1.0
|
||||||
|
#define SAMPLE_Y_SPACING (SAMPLE_HEIGHT + SAMPLE_BORDER * 2.0)
|
||||||
|
#define SAMPLE_Y_OFFSET (CANVAS_BORDER + 1.0)
|
||||||
|
|
||||||
|
struct Viewport
|
||||||
|
{
|
||||||
|
float width;
|
||||||
|
float height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TimeRange
|
||||||
|
{
|
||||||
|
float usStart;
|
||||||
|
float usPerPixel;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Row
|
||||||
|
{
|
||||||
|
float yOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform Viewport inViewport;
|
||||||
|
uniform TimeRange inTimeRange;
|
||||||
|
uniform Row inRow;
|
||||||
|
|
||||||
|
in vec4 inSample_TextOffset;
|
||||||
|
in vec4 inColour_TextLength;
|
||||||
|
|
||||||
|
out vec4 varColour_TimeMs;
|
||||||
|
out vec4 varPosInBoxPx_TextEntry;
|
||||||
|
out float varTimeChars;
|
||||||
|
|
||||||
|
//#define PIXEL_ROUNDED_OFFSETS
|
||||||
|
|
||||||
|
float PixelOffset(float time_us)
|
||||||
|
{
|
||||||
|
float offset = (time_us - inTimeRange.usStart) * inTimeRange.usPerPixel;
|
||||||
|
#ifdef PIXEL_ROUNDED_OFFSETS
|
||||||
|
return floor(offset);
|
||||||
|
#else
|
||||||
|
return offset;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
float PixelSize(float time_us)
|
||||||
|
{
|
||||||
|
float size = time_us * inTimeRange.usPerPixel;
|
||||||
|
#ifdef PIXEL_ROUNDED_OFFSETS
|
||||||
|
return floor(size);
|
||||||
|
#else
|
||||||
|
return size;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// Unpack input data
|
||||||
|
float us_start = inSample_TextOffset.x;
|
||||||
|
float us_length = inSample_TextOffset.y;
|
||||||
|
float depth = inSample_TextOffset.z;
|
||||||
|
float text_buffer_offset = inSample_TextOffset.w;
|
||||||
|
vec3 box_colour = inColour_TextLength.rgb;
|
||||||
|
float text_length_chars = inColour_TextLength.w;
|
||||||
|
|
||||||
|
// Determine pixel range of the sample
|
||||||
|
float x0 = PixelOffset(us_start);
|
||||||
|
float x1 = x0 + PixelSize(us_length);
|
||||||
|
|
||||||
|
// Calculate box to render
|
||||||
|
float offset_x = x0;
|
||||||
|
float offset_y = inRow.yOffset + SAMPLE_Y_OFFSET + (depth - 1.0) * SAMPLE_Y_SPACING;
|
||||||
|
float size_x = max(x1 - x0, 1.0);
|
||||||
|
float size_y = SAMPLE_HEIGHT;
|
||||||
|
|
||||||
|
// Box range
|
||||||
|
float min_x = offset_x;
|
||||||
|
float min_y = offset_y;
|
||||||
|
float max_x = offset_x + size_x;
|
||||||
|
float max_y = offset_y + size_y;
|
||||||
|
|
||||||
|
// Quad indices are:
|
||||||
|
//
|
||||||
|
// 2 3
|
||||||
|
// +----+
|
||||||
|
// | |
|
||||||
|
// +----+
|
||||||
|
// 0 1
|
||||||
|
//
|
||||||
|
vec2 position;
|
||||||
|
position.x = (gl_VertexID & 1) == 0 ? min_x : max_x;
|
||||||
|
position.y = (gl_VertexID & 2) == 0 ? min_y : max_y;
|
||||||
|
|
||||||
|
//
|
||||||
|
// NDC is:
|
||||||
|
// -1 to 1, left to right
|
||||||
|
// -1 to 1, bottom to top
|
||||||
|
//
|
||||||
|
vec4 ndc_pos;
|
||||||
|
ndc_pos.x = (position.x / inViewport.width) * 2.0 - 1.0;
|
||||||
|
ndc_pos.y = 1.0 - (position.y / inViewport.height) * 2.0;
|
||||||
|
ndc_pos.z = 0.0;
|
||||||
|
ndc_pos.w = 1.0;
|
||||||
|
|
||||||
|
// Calculate number of characters required to display the millisecond time
|
||||||
|
float time_ms = us_length / 1000.0;
|
||||||
|
float time_ms_int = floor(time_ms);
|
||||||
|
float time_chars = time_ms_int == 0.0 ? 1.0 : floor(log(time_ms_int) / 2.302585092994046) + 1.0;
|
||||||
|
|
||||||
|
gl_Position = ndc_pos;
|
||||||
|
|
||||||
|
varColour_TimeMs = vec4(box_colour / 255.0, time_ms);
|
||||||
|
varPosInBoxPx_TextEntry = vec4(position.x - offset_x, position.y - offset_y, text_buffer_offset, text_length_chars);
|
||||||
|
varTimeChars = time_chars;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TimelineFShader = `#version 300 es
|
||||||
|
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
#define SAMPLE_HEIGHT 16.0
|
||||||
|
|
||||||
|
struct TextBufferDesc
|
||||||
|
{
|
||||||
|
float fontWidth;
|
||||||
|
float fontHeight;
|
||||||
|
float textBufferLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform sampler2D inFontAtlasTextre;
|
||||||
|
uniform sampler2D inTextBuffer;
|
||||||
|
uniform TextBufferDesc inTextBufferDesc;
|
||||||
|
|
||||||
|
in vec4 varColour_TimeMs;
|
||||||
|
in vec4 varPosInBoxPx_TextEntry;
|
||||||
|
in float varTimeChars;
|
||||||
|
|
||||||
|
out vec4 outColour;
|
||||||
|
|
||||||
|
vec4 LookupCharacter(float char_ascii, float pos_x, float pos_y, float font_width_px, float font_height_px)
|
||||||
|
{
|
||||||
|
// 2D index of the ASCII character in the font atlas
|
||||||
|
float char_index_y = floor(char_ascii / 16.0);
|
||||||
|
float char_index_x = char_ascii - char_index_y * 16.0;
|
||||||
|
|
||||||
|
// Start UV of the character in the font atlas
|
||||||
|
float char_base_uv_x = char_index_x / 16.0;
|
||||||
|
float char_base_uv_y = char_index_y / 16.0;
|
||||||
|
|
||||||
|
// UV within the character itself, scaled to the font atlas
|
||||||
|
float char_uv_x = pos_x / (font_width_px * 16.0);
|
||||||
|
float char_uv_y = pos_y / (font_height_px * 16.0);
|
||||||
|
|
||||||
|
vec2 uv;
|
||||||
|
uv.x = char_base_uv_x + char_uv_x;
|
||||||
|
uv.y = char_base_uv_y + char_uv_y;
|
||||||
|
|
||||||
|
// Apply colour to the text in premultiplied alpha space
|
||||||
|
vec4 t = texture(inFontAtlasTextre, uv);
|
||||||
|
vec3 colour = vec3(1.0, 1.0, 1.0) * 0.25;
|
||||||
|
return vec4(colour * t.a, t.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// Font description
|
||||||
|
float font_width_px = inTextBufferDesc.fontWidth;
|
||||||
|
float font_height_px = inTextBufferDesc.fontHeight;
|
||||||
|
float text_buffer_length = inTextBufferDesc.textBufferLength;
|
||||||
|
|
||||||
|
// Text range in the text buffer
|
||||||
|
vec2 pos_in_box_px = varPosInBoxPx_TextEntry.xy;
|
||||||
|
float text_buffer_offset = varPosInBoxPx_TextEntry.z;
|
||||||
|
float text_length_chars = varPosInBoxPx_TextEntry.w;
|
||||||
|
float text_length_px = text_length_chars * font_width_px;
|
||||||
|
|
||||||
|
// Text placement offset within the box
|
||||||
|
const vec2 text_offset_px = vec2(4.0, 3.0);
|
||||||
|
|
||||||
|
vec4 box_colour = vec4(varColour_TimeMs.rgb, 1.0);
|
||||||
|
|
||||||
|
// Add a subtle border to the box so that you can visually separate samples when they are next to each other
|
||||||
|
vec2 top_left = min(pos_in_box_px.xy, 2.0);
|
||||||
|
float both = min(top_left.x, top_left.y);
|
||||||
|
box_colour.rgb *= (0.8 + both * 0.1);
|
||||||
|
|
||||||
|
vec4 text_colour = vec4(0.0);
|
||||||
|
|
||||||
|
float text_end_px = text_length_px + text_offset_px.x + font_width_px;
|
||||||
|
float time_length_px = (varTimeChars + 4.0) * font_width_px;
|
||||||
|
if (pos_in_box_px.x > text_end_px && pos_in_box_px.x < text_end_px + time_length_px)
|
||||||
|
{
|
||||||
|
float time_ms = varColour_TimeMs.w;
|
||||||
|
|
||||||
|
vec2 time_pixel_pos;
|
||||||
|
time_pixel_pos.x = max(min(pos_in_box_px.x - text_end_px, time_length_px), 0.0);
|
||||||
|
time_pixel_pos.y = max(min(pos_in_box_px.y - text_offset_px.y, font_height_px - 1.0), 0.0);
|
||||||
|
|
||||||
|
float time_index = floor(time_pixel_pos.x / font_width_px);
|
||||||
|
if (time_index < varTimeChars)
|
||||||
|
{
|
||||||
|
// Use base-10 integer digit counting to calculate the divisor needed to bring this digit below 10
|
||||||
|
float time_divisor = 1.0;
|
||||||
|
for (int i = 0; i < int(varTimeChars - time_index - 1.0); i++)
|
||||||
|
{
|
||||||
|
time_divisor *= 10.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate digit
|
||||||
|
float time_shifted_int = floor(time_ms / time_divisor);
|
||||||
|
float time_digit = floor(mod(time_shifted_int, 10.0));
|
||||||
|
|
||||||
|
text_colour = LookupCharacter(48.0 + time_digit,
|
||||||
|
time_pixel_pos.x - time_index * font_width_px,
|
||||||
|
time_pixel_pos.y,
|
||||||
|
font_width_px, font_height_px);
|
||||||
|
}
|
||||||
|
else if (time_index == varTimeChars)
|
||||||
|
{
|
||||||
|
text_colour = LookupCharacter(46.0,
|
||||||
|
time_pixel_pos.x - time_index * font_width_px,
|
||||||
|
time_pixel_pos.y,
|
||||||
|
font_width_px, font_height_px);
|
||||||
|
}
|
||||||
|
else if (time_index == varTimeChars + 1.0)
|
||||||
|
{
|
||||||
|
float time_digit = floor(mod(time_ms * 10.0, 10.0));
|
||||||
|
text_colour = LookupCharacter(48.0 + time_digit,
|
||||||
|
time_pixel_pos.x - time_index * font_width_px,
|
||||||
|
time_pixel_pos.y,
|
||||||
|
font_width_px, font_height_px);
|
||||||
|
}
|
||||||
|
else if (time_index == varTimeChars + 2.0)
|
||||||
|
{
|
||||||
|
float time_digit = floor(mod(time_ms * 10.0, 10.0));
|
||||||
|
text_colour = LookupCharacter(109.0,
|
||||||
|
time_pixel_pos.x - time_index * font_width_px,
|
||||||
|
time_pixel_pos.y,
|
||||||
|
font_width_px, font_height_px);
|
||||||
|
}
|
||||||
|
else if (time_index == varTimeChars + 3.0)
|
||||||
|
{
|
||||||
|
float time_digit = floor(mod(time_ms * 10.0, 10.0));
|
||||||
|
text_colour = LookupCharacter(115.0, time_pixel_pos.x - time_index * font_width_px, time_pixel_pos.y, font_width_px, font_height_px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Text pixel position clamped to the bounds of the full word, allowing leakage to neighbouring NULL characters to pad zeroes
|
||||||
|
vec2 text_pixel_pos;
|
||||||
|
text_pixel_pos.x = max(min(pos_in_box_px.x - text_offset_px.x, text_length_px), -1.0);
|
||||||
|
text_pixel_pos.y = max(min(pos_in_box_px.y - text_offset_px.y, font_height_px - 1.0), 0.0);
|
||||||
|
|
||||||
|
// Index of the current character in the text buffer
|
||||||
|
float text_index = text_buffer_offset + floor(text_pixel_pos.x / font_width_px);
|
||||||
|
|
||||||
|
// Sample the 1D text buffer to get the ASCII character index
|
||||||
|
vec2 char_uv = vec2((text_index + 0.5) / text_buffer_length, 0.5);
|
||||||
|
float char_ascii = texture(inTextBuffer, char_uv).a * 255.0;
|
||||||
|
|
||||||
|
text_colour = LookupCharacter(char_ascii,
|
||||||
|
text_pixel_pos.x - (text_index - text_buffer_offset) * font_width_px,
|
||||||
|
text_pixel_pos.y,
|
||||||
|
font_width_px, font_height_px);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring out of premultiplied alpha space and lerp with the box colour
|
||||||
|
float inv_alpha = text_colour.a == 0.0 ? 1.0 : 1.0 / text_colour.a;
|
||||||
|
outColour = mix(box_colour, vec4(text_colour.rgb * inv_alpha, 1.0), text_colour.a);
|
||||||
|
}
|
||||||
|
`;
|
||||||
29
vis/Code/ThreadFrame.js
Normal file
29
vis/Code/ThreadFrame.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
|
||||||
|
ThreadFrame = (function()
|
||||||
|
{
|
||||||
|
function ThreadFrame(message)
|
||||||
|
{
|
||||||
|
// Persist the required message data
|
||||||
|
this.NbSamples = message.nb_samples;
|
||||||
|
this.SampleDigest = message.sample_digest;
|
||||||
|
this.Samples = message.samples;
|
||||||
|
this.PartialTree = message.partial_tree > 0 ? true : false;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
})();
|
||||||
186
vis/Code/TimelineMarkers.js
Normal file
186
vis/Code/TimelineMarkers.js
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
|
||||||
|
function GetTimeText(seconds)
|
||||||
|
{
|
||||||
|
if (seconds < 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = "";
|
||||||
|
|
||||||
|
// Add any contributing hours
|
||||||
|
var h = Math.floor(seconds / 3600);
|
||||||
|
seconds -= h * 3600;
|
||||||
|
if (h)
|
||||||
|
{
|
||||||
|
text += h + "h ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any contributing minutes
|
||||||
|
var m = Math.floor(seconds / 60);
|
||||||
|
seconds -= m * 60;
|
||||||
|
if (m)
|
||||||
|
{
|
||||||
|
text += m + "m ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any contributing seconds or always add seconds when hours or minutes have no contribution
|
||||||
|
// This ensures the 0s marker displays
|
||||||
|
var s = Math.floor(seconds);
|
||||||
|
seconds -= s;
|
||||||
|
if (s || text == "")
|
||||||
|
{
|
||||||
|
text += s + "s ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add remaining milliseconds
|
||||||
|
var ms = Math.floor(seconds * 1000);
|
||||||
|
if (ms)
|
||||||
|
{
|
||||||
|
text += ms + "ms";
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TimelineMarkers
|
||||||
|
{
|
||||||
|
constructor(timeline)
|
||||||
|
{
|
||||||
|
this.timeline = timeline;
|
||||||
|
|
||||||
|
// Need a 2D drawing context
|
||||||
|
this.markerContainer = timeline.Window.AddControlNew(new WM.Container(10, 10, 10, 10));
|
||||||
|
this.markerCanvas = document.createElement("canvas");
|
||||||
|
this.markerContainer.Node.appendChild(this.markerCanvas);
|
||||||
|
this.markerContext = this.markerCanvas.getContext("2d");
|
||||||
|
}
|
||||||
|
|
||||||
|
Draw(time_range)
|
||||||
|
{
|
||||||
|
let ctx = this.markerContext;
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, this.markerCanvas.width, this.markerCanvas.height);
|
||||||
|
|
||||||
|
// Setup render state for the time line markers
|
||||||
|
ctx.strokeStyle = "#BBB";
|
||||||
|
ctx.fillStyle = "#BBB";
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.font = "9px LocalFiraCode";
|
||||||
|
|
||||||
|
// A list of all supported units of time (measured in seconds) that require markers
|
||||||
|
let units = [ 0.001, 0.01, 0.1, 1, 10, 60, 60 * 5, 60 * 60, 60 * 60 * 24 ];
|
||||||
|
|
||||||
|
// Given the current pixel size of a second, calculate the spacing for each unit marker
|
||||||
|
let second_pixel_size = time_range.PixelSize(1000 * 1000);
|
||||||
|
let sizeof_units = [ ];
|
||||||
|
for (let unit of units)
|
||||||
|
{
|
||||||
|
sizeof_units.push(unit * second_pixel_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate whether each unit marker is visible at the current zoom level
|
||||||
|
var show_unit = [ ];
|
||||||
|
for (let sizeof_unit of sizeof_units)
|
||||||
|
{
|
||||||
|
show_unit.push(Math.max(Math.min((sizeof_unit - 4) * 0.25, 1), 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first visible unit
|
||||||
|
for (let i = 0; i < units.length; i++)
|
||||||
|
{
|
||||||
|
if (show_unit[i] > 0)
|
||||||
|
{
|
||||||
|
// Cut out unit information for the first set of units not visible
|
||||||
|
units = units.slice(i);
|
||||||
|
sizeof_units = sizeof_units.slice(i);
|
||||||
|
show_unit = show_unit.slice(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeline_end = this.markerCanvas.width;
|
||||||
|
for (let i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
// Round the start time up to the next visible unit
|
||||||
|
let time_start = time_range.Start_us / (1000 * 1000);
|
||||||
|
let unit_time_start = Math.ceil(time_start / units[i]) * units[i];
|
||||||
|
|
||||||
|
// Calculate the canvas offset required to step to the first visible unit
|
||||||
|
let pre_step_x = time_range.PixelOffset(unit_time_start * (1000 * 1000));
|
||||||
|
|
||||||
|
// Draw lines for every unit at this level, keeping tracking of the seconds
|
||||||
|
var seconds = unit_time_start;
|
||||||
|
for (let x = pre_step_x; x <= timeline_end; x += sizeof_units[i])
|
||||||
|
{
|
||||||
|
// For the first two units, don't draw the units above it to prevent
|
||||||
|
// overdraw and the visual errors that causes
|
||||||
|
// The last unit always draws
|
||||||
|
if (i > 1 || (seconds % units[i + 1]))
|
||||||
|
{
|
||||||
|
// Only the first two units scale with unit visibility
|
||||||
|
// The last unit maintains its size
|
||||||
|
let height = Math.min(i * 4 + 4 * show_unit[i], 16);
|
||||||
|
|
||||||
|
// Draw the line on an integer boundary, shifted by 0.5 to get an un-anti-aliased 1px line
|
||||||
|
let ix = Math.floor(x);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(ix + 0.5, 1);
|
||||||
|
ctx.lineTo(ix + 0.5, 1 + height);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
seconds += units[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 1)
|
||||||
|
{
|
||||||
|
// Draw text labels for the second unit, fading them out as they slowly
|
||||||
|
// become the first unit
|
||||||
|
ctx.globalAlpha = show_unit[0];
|
||||||
|
var seconds = unit_time_start;
|
||||||
|
for (let x = pre_step_x; x <= timeline_end; x += sizeof_units[i])
|
||||||
|
{
|
||||||
|
if (seconds % units[2])
|
||||||
|
{
|
||||||
|
this.DrawTimeText(seconds, x, 16);
|
||||||
|
}
|
||||||
|
seconds += units[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore alpha
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (i == 2)
|
||||||
|
{
|
||||||
|
// Draw text labels for the third unit with no fade
|
||||||
|
var seconds = unit_time_start;
|
||||||
|
for (let x = pre_step_x; x <= timeline_end; x += sizeof_units[i])
|
||||||
|
{
|
||||||
|
this.DrawTimeText(seconds, x, 16);
|
||||||
|
seconds += units[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawTimeText(seconds, x, y)
|
||||||
|
{
|
||||||
|
// Use text measuring to centre the text horizontally on the input x
|
||||||
|
var text = GetTimeText(seconds);
|
||||||
|
var width = this.markerContext.measureText(text).width;
|
||||||
|
this.markerContext.fillText(text, Math.floor(x) - width / 2, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Resize(x, y, w, h)
|
||||||
|
{
|
||||||
|
this.markerContainer.SetPosition(x, y);
|
||||||
|
this.markerContainer.SetSize(w, h);
|
||||||
|
|
||||||
|
// Match canvas size to container
|
||||||
|
this.markerCanvas.width = this.markerContainer.Node.clientWidth;
|
||||||
|
this.markerCanvas.height = this.markerContainer.Node.clientHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
389
vis/Code/TimelineRow.js
Normal file
389
vis/Code/TimelineRow.js
Normal file
|
|
@ -0,0 +1,389 @@
|
||||||
|
|
||||||
|
|
||||||
|
TimelineRow = (function()
|
||||||
|
{
|
||||||
|
const RowLabelTemplate = `
|
||||||
|
<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>
|
||||||
|
<div style="clear:left"></div>
|
||||||
|
</div>`
|
||||||
|
|
||||||
|
|
||||||
|
var CANVAS_BORDER = 1;
|
||||||
|
var SAMPLE_HEIGHT = 16;
|
||||||
|
var SAMPLE_BORDER = 1;
|
||||||
|
var SAMPLE_Y_SPACING = SAMPLE_HEIGHT + SAMPLE_BORDER * 2;
|
||||||
|
|
||||||
|
|
||||||
|
function TimelineRow(gl, name, timeline, frame_history, check_handler)
|
||||||
|
{
|
||||||
|
this.Name = name;
|
||||||
|
this.timeline = timeline;
|
||||||
|
|
||||||
|
// Create the row HTML and add to the parent
|
||||||
|
this.LabelContainerNode = DOM.Node.CreateHTML(RowLabelTemplate);
|
||||||
|
const label_node = DOM.Node.FindWithClass(this.LabelContainerNode, "TimelineRowLabel");
|
||||||
|
label_node.innerHTML = name;
|
||||||
|
timeline.TimelineLabels.Node.appendChild(this.LabelContainerNode);
|
||||||
|
|
||||||
|
// All sample view windows visible by default
|
||||||
|
const checkbox_node = DOM.Node.FindWithClass(this.LabelContainerNode, "TimelineRowCheckbox");
|
||||||
|
checkbox_node.checked = true;
|
||||||
|
checkbox_node.addEventListener("change", (e) => check_handler(name, e));
|
||||||
|
|
||||||
|
// Manually hook-up events to simulate div:active
|
||||||
|
// I can't get the equivalent CSS to work in Firefox, so...
|
||||||
|
const expand_node_0 = DOM.Node.FindWithClass(this.LabelContainerNode, "TimelineRowExpand", 0);
|
||||||
|
const expand_node_1 = DOM.Node.FindWithClass(this.LabelContainerNode, "TimelineRowExpand", 1);
|
||||||
|
const inc_node = DOM.Node.FindWithClass(expand_node_0, "TimelineRowExpandButton");
|
||||||
|
const dec_node = DOM.Node.FindWithClass(expand_node_1, "TimelineRowExpandButton");
|
||||||
|
inc_node.addEventListener("mousedown", ExpandButtonDown);
|
||||||
|
inc_node.addEventListener("mouseup", ExpandButtonUp);
|
||||||
|
inc_node.addEventListener("mouseleave", ExpandButtonUp);
|
||||||
|
dec_node.addEventListener("mousedown", ExpandButtonDown);
|
||||||
|
dec_node.addEventListener("mouseup", ExpandButtonUp);
|
||||||
|
dec_node.addEventListener("mouseleave", ExpandButtonUp);
|
||||||
|
|
||||||
|
// Pressing +/i increases/decreases depth
|
||||||
|
inc_node.addEventListener("click", () => this.IncDepth());
|
||||||
|
dec_node.addEventListener("click", () => this.DecDepth());
|
||||||
|
|
||||||
|
// Frame index to start at when looking for first visible sample
|
||||||
|
this.StartFrameIndex = 0;
|
||||||
|
|
||||||
|
this.FrameHistory = frame_history;
|
||||||
|
this.VisibleFrames = [ ];
|
||||||
|
this.VisibleTimeRange = null;
|
||||||
|
this.Depth = 1;
|
||||||
|
|
||||||
|
// Currently selected sample
|
||||||
|
this.SelectSampleInfo = null;
|
||||||
|
|
||||||
|
// Create WebGL sample buffers
|
||||||
|
this.sampleBuffer = new glDynamicBuffer(gl, gl.FLOAT, 4, 8);
|
||||||
|
this.colourBuffer = new glDynamicBuffer(gl, gl.FLOAT, 4, 8);
|
||||||
|
|
||||||
|
// Create a vertex array for these buffers
|
||||||
|
this.vertexArrayObject = gl.createVertexArray();
|
||||||
|
gl.bindVertexArray(this.vertexArrayObject);
|
||||||
|
this.sampleBuffer.BindAsInstanceAttribute(timeline.Program, "inSample_TextOffset");
|
||||||
|
this.colourBuffer.BindAsInstanceAttribute(timeline.Program, "inColour_TextLength");
|
||||||
|
|
||||||
|
// An initial SetSize call to restore containers to their original size after traces were loaded prior to this
|
||||||
|
this.SetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineRow.prototype.SetSize = function()
|
||||||
|
{
|
||||||
|
this.LabelContainerNode.style.height = CANVAS_BORDER + SAMPLE_BORDER + SAMPLE_Y_SPACING * this.Depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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.DrawSampleHighlight = function(sample, depth, colour, y_scroll_offset)
|
||||||
|
{
|
||||||
|
if (depth <= this.Depth)
|
||||||
|
{
|
||||||
|
// Determine pixel range of the sample
|
||||||
|
var x0 = this.VisibleTimeRange.PixelOffset(sample.us_start);
|
||||||
|
var x1 = x0 + this.VisibleTimeRange.PixelSize(sample.us_length);
|
||||||
|
|
||||||
|
var offset_x = x0;
|
||||||
|
var offset_y = this.LabelContainerNode.offsetTop + 2 + (depth - 1) * SAMPLE_Y_SPACING + y_scroll_offset;
|
||||||
|
var size_x = x1 - x0;
|
||||||
|
var size_y = SAMPLE_HEIGHT;
|
||||||
|
|
||||||
|
// Normal rendering
|
||||||
|
var ctx = this.timeline.drawContext;
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.strokeStyle = colour;
|
||||||
|
ctx.strokeRect(offset_x + 2.5, offset_y - 0.5, size_x - 3, size_y + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineRow.prototype.DisplayHeight = function()
|
||||||
|
{
|
||||||
|
return this.LabelContainerNode.clientHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineRow.prototype.YOffset = function()
|
||||||
|
{
|
||||||
|
return this.LabelContainerNode.offsetTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineRow.prototype.DrawBackground = function(hover_sample_info, y_scroll_offset)
|
||||||
|
{
|
||||||
|
// Fill box that shows the boundary between thread rows
|
||||||
|
this.timeline.drawContext.fillStyle = "#444"
|
||||||
|
var b = CANVAS_BORDER;
|
||||||
|
this.timeline.drawContext.fillRect(b, this.YOffset() + y_scroll_offset + b, this.timeline.drawCanvas.width - b * 2, this.DisplayHeight() - b * 2);
|
||||||
|
|
||||||
|
// Draw the selected sample for this row
|
||||||
|
if (this.SelectSampleInfo != null)
|
||||||
|
{
|
||||||
|
const sample = this.SelectSampleInfo[1];
|
||||||
|
const depth = this.SelectSampleInfo[2];
|
||||||
|
this.DrawSampleHighlight(sample, depth, "#FF0000", y_scroll_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the current hover sample if it's over this row
|
||||||
|
if (hover_sample_info != null && hover_sample_info[3] == this)
|
||||||
|
{
|
||||||
|
const sample = hover_sample_info[1];
|
||||||
|
const depth = hover_sample_info[2];
|
||||||
|
const thread_row = hover_sample_info[3];
|
||||||
|
this.DrawSampleHighlight(sample, depth, "#FFFFFF", y_scroll_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineRow.prototype.Draw = function(gl, draw_text, y_scroll_offset)
|
||||||
|
{
|
||||||
|
let samples_per_depth = [];
|
||||||
|
|
||||||
|
// Gather all root samples in the visible frame set
|
||||||
|
for (var i in this.VisibleFrames)
|
||||||
|
{
|
||||||
|
var frame = this.VisibleFrames[i];
|
||||||
|
GatherSamples(this, frame.Samples, 1, draw_text, samples_per_depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count number of samples required
|
||||||
|
let nb_samples = 0;
|
||||||
|
for (const samples_this_depth of samples_per_depth)
|
||||||
|
{
|
||||||
|
nb_samples += samples_this_depth.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize buffers to match any new count of samples
|
||||||
|
if (nb_samples > this.sampleBuffer.nbEntries)
|
||||||
|
{
|
||||||
|
this.sampleBuffer.ResizeToFitNextPow2(nb_samples);
|
||||||
|
this.colourBuffer.ResizeToFitNextPow2(nb_samples);
|
||||||
|
|
||||||
|
// Have to create a new VAO for these buffers
|
||||||
|
this.vertexArrayObject = gl.createVertexArray();
|
||||||
|
gl.bindVertexArray(this.vertexArrayObject);
|
||||||
|
this.sampleBuffer.BindAsInstanceAttribute(this.timeline.Program, "inSample_TextOffset");
|
||||||
|
this.colourBuffer.BindAsInstanceAttribute(this.timeline.Program, "inColour_TextLength");
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU write destination for samples
|
||||||
|
let cpu_samples = this.sampleBuffer.cpuArray;
|
||||||
|
let cpu_colours = this.colourBuffer.cpuArray;
|
||||||
|
let sample_pos = 0;
|
||||||
|
|
||||||
|
const empty_text_entry = {
|
||||||
|
offset: 0,
|
||||||
|
length: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy samples to the CPU buffer
|
||||||
|
// TODO(don): Use a ring buffer instead and take advantage of timeline scrolling adding new samples at the beginning/end
|
||||||
|
for (let depth = 0; depth < samples_per_depth.length; depth++)
|
||||||
|
{
|
||||||
|
let samples_this_depth = samples_per_depth[depth];
|
||||||
|
for (const sample of samples_this_depth)
|
||||||
|
{
|
||||||
|
const text_entry = sample.name.textEntry != null ? sample.name.textEntry : empty_text_entry;
|
||||||
|
|
||||||
|
cpu_samples[sample_pos + 0] = sample.us_start;
|
||||||
|
cpu_samples[sample_pos + 1] = sample.us_length;
|
||||||
|
cpu_samples[sample_pos + 2] = depth;
|
||||||
|
cpu_samples[sample_pos + 3] = text_entry.offset;
|
||||||
|
|
||||||
|
cpu_colours[sample_pos + 0] = sample.rgbColour[0];
|
||||||
|
cpu_colours[sample_pos + 1] = sample.rgbColour[1];
|
||||||
|
cpu_colours[sample_pos + 2] = sample.rgbColour[2];
|
||||||
|
cpu_colours[sample_pos + 3] = text_entry.length;
|
||||||
|
|
||||||
|
sample_pos += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload to GPU
|
||||||
|
this.sampleBuffer.UploadData();
|
||||||
|
this.colourBuffer.UploadData();
|
||||||
|
this.timeline.textBuffer.UploadData();
|
||||||
|
|
||||||
|
// Set row parameters
|
||||||
|
glSetUniform(gl, this.timeline.Program, "inRow.yOffset", this.YOffset() + y_scroll_offset);
|
||||||
|
|
||||||
|
gl.bindVertexArray(this.vertexArrayObject);
|
||||||
|
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, nb_samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GatherSamples(self, samples, depth, draw_text, samples_per_depth)
|
||||||
|
{
|
||||||
|
// Ensure there's enough entries for each depth
|
||||||
|
while (depth >= samples_per_depth.length)
|
||||||
|
{
|
||||||
|
samples_per_depth.push([]);
|
||||||
|
}
|
||||||
|
let samples_this_depth = samples_per_depth[depth];
|
||||||
|
|
||||||
|
for (var i in samples)
|
||||||
|
{
|
||||||
|
var sample = samples[i];
|
||||||
|
samples_this_depth.push(sample);
|
||||||
|
|
||||||
|
if (depth < self.Depth && sample.children != null)
|
||||||
|
GatherSamples(self, sample.children, depth + 1, draw_text, samples_per_depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineRow.prototype.SetSelectSample = function(sample_info)
|
||||||
|
{
|
||||||
|
this.SelectSampleInfo = sample_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineRow.prototype.IncDepth = function()
|
||||||
|
{
|
||||||
|
this.Depth++;
|
||||||
|
this.SetSize();
|
||||||
|
this.timeline.DrawAllRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineRow.prototype.DecDepth = function()
|
||||||
|
{
|
||||||
|
if (this.Depth > 1)
|
||||||
|
{
|
||||||
|
this.Depth--;
|
||||||
|
this.SetSize();
|
||||||
|
|
||||||
|
// Trigger scroll handling to ensure reducing the depth reduces the display height
|
||||||
|
this.timeline.ScrollVertically(0);
|
||||||
|
|
||||||
|
this.timeline.DrawAllRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineRow.prototype.GetSampleAtPosition = function(time_us, mouse_y)
|
||||||
|
{
|
||||||
|
// Calculate depth of the mouse cursor
|
||||||
|
var depth = Math.min(Math.floor(mouse_y / SAMPLE_Y_SPACING) + 1, this.Depth);
|
||||||
|
|
||||||
|
// Search for the first frame to intersect this time
|
||||||
|
for (var i in this.VisibleFrames)
|
||||||
|
{
|
||||||
|
// Use the sample's closed interval to detect hits.
|
||||||
|
// Rendering of samples ensures a sample is never smaller than one pixel so that all samples always draw, irrespective
|
||||||
|
// of zoom level. If a half-open interval is used then some visible samples will be unselectable due to them being
|
||||||
|
// smaller than a pixel. This feels pretty odd and the closed interval fixes this feeling well.
|
||||||
|
// TODO(don): There are still inconsistencies, need to shift to pixel range checking to match exactly.
|
||||||
|
var frame = this.VisibleFrames[i];
|
||||||
|
if (time_us >= frame.StartTime_us && time_us <= frame.EndTime_us)
|
||||||
|
{
|
||||||
|
var found_sample = FindSample(this, frame.Samples, time_us, depth, 1);
|
||||||
|
if (found_sample != null)
|
||||||
|
return [ frame, found_sample[0], found_sample[1], this ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return TimelineRow;
|
||||||
|
})();
|
||||||
494
vis/Code/TimelineWindow.js
Normal file
494
vis/Code/TimelineWindow.js
Normal file
|
|
@ -0,0 +1,494 @@
|
||||||
|
|
||||||
|
// TODO(don): Separate all knowledge of threads from this timeline
|
||||||
|
|
||||||
|
TimelineWindow = (function()
|
||||||
|
{
|
||||||
|
var BORDER = 10;
|
||||||
|
|
||||||
|
function TimelineWindow(wm, name, settings, check_handler)
|
||||||
|
{
|
||||||
|
this.Settings = settings;
|
||||||
|
|
||||||
|
// Create timeline window
|
||||||
|
this.Window = wm.AddWindow("Timeline", 10, 20, 100, 100);
|
||||||
|
this.Window.SetTitle(name);
|
||||||
|
this.Window.ShowNoAnim();
|
||||||
|
|
||||||
|
this.timelineMarkers = new TimelineMarkers(this);
|
||||||
|
|
||||||
|
// DO THESE need to be containers... can they just be divs?
|
||||||
|
// divs need a retrieval function
|
||||||
|
this.TimelineLabelScrollClipper = this.Window.AddControlNew(new WM.Container(10, 10, 10, 10));
|
||||||
|
DOM.Node.AddClass(this.TimelineLabelScrollClipper.Node, "TimelineLabelScrollClipper");
|
||||||
|
this.TimelineLabels = this.TimelineLabelScrollClipper.AddControlNew(new WM.Container(0, 0, 10, 10));
|
||||||
|
DOM.Node.AddClass(this.TimelineLabels.Node, "TimelineLabels");
|
||||||
|
|
||||||
|
// Ordered list of thread rows on the timeline
|
||||||
|
this.ThreadRows = [ ];
|
||||||
|
|
||||||
|
// Create timeline container
|
||||||
|
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;
|
||||||
|
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));
|
||||||
|
DOM.Event.AddHandler(this.TimelineContainer.Node, "mouseleave", Bind(OnMouseLeave, this));
|
||||||
|
|
||||||
|
// Create a canvas for timeline 2D rendering
|
||||||
|
// TODO(don): Port this to shaders
|
||||||
|
this.drawCanvas = document.createElement("canvas");
|
||||||
|
this.drawCanvas.width = this.TimelineContainer.Node.clientWidth;
|
||||||
|
this.drawCanvas.height = this.TimelineContainer.Node.clientHeight;
|
||||||
|
this.TimelineContainer.Node.appendChild(this.drawCanvas);
|
||||||
|
this.drawContext = this.drawCanvas.getContext("2d");
|
||||||
|
|
||||||
|
// Create a canvas for timeline 3D accelerated rendering
|
||||||
|
this.glCanvas = document.createElement("canvas");
|
||||||
|
this.glCanvas.width = this.TimelineContainer.Node.clientWidth;
|
||||||
|
this.glCanvas.height = this.TimelineContainer.Node.clientHeight;
|
||||||
|
this.TimelineContainer.Node.appendChild(this.glCanvas);
|
||||||
|
|
||||||
|
// OVERLAY - add to CSS
|
||||||
|
this.glCanvas.style.position = "absolute";
|
||||||
|
this.glCanvas.style.top = 0;
|
||||||
|
this.glCanvas.style.left = 0;
|
||||||
|
|
||||||
|
// For now a gl context per timeline
|
||||||
|
let gl = this.glCanvas.getContext("webgl2");
|
||||||
|
this.gl = gl;
|
||||||
|
|
||||||
|
const vshader = glCompileShader(gl, gl.VERTEX_SHADER, "TimelineVShader", TimelineVShader);
|
||||||
|
const fshader = glCompileShader(gl, gl.FRAGMENT_SHADER, "TimelineFShader", TimelineFShader);
|
||||||
|
this.Program = glCreateProgram(gl, vshader, fshader);
|
||||||
|
|
||||||
|
this.font = new glFont(gl);
|
||||||
|
this.textBuffer = new glTextBuffer(gl, this.font);
|
||||||
|
|
||||||
|
this.Window.SetOnResize(Bind(OnUserResize, this));
|
||||||
|
|
||||||
|
this.Clear();
|
||||||
|
|
||||||
|
this.OnHoverHandler = null;
|
||||||
|
this.OnSelectedHandler = null;
|
||||||
|
this.OnMovedHandler = null;
|
||||||
|
this.CheckHandler = check_handler;
|
||||||
|
|
||||||
|
this.yScrollOffset = 0;
|
||||||
|
|
||||||
|
this.HoverSampleInfo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.Clear = function()
|
||||||
|
{
|
||||||
|
// Clear out labels
|
||||||
|
this.TimelineLabels.ClearControls();
|
||||||
|
|
||||||
|
this.ThreadRows = [ ];
|
||||||
|
this.TimeRange = new PixelTimeRange(0, 200 * 1000, this.TimelineContainer.Node.clientWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.SetOnHover = function(handler)
|
||||||
|
{
|
||||||
|
this.OnHoverHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.SetOnSelected = function(handler)
|
||||||
|
{
|
||||||
|
this.OnSelectedHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.SetOnMoved = function(handler)
|
||||||
|
{
|
||||||
|
this.OnMovedHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.WindowResized = function(x, width, top_window)
|
||||||
|
{
|
||||||
|
// Resize window
|
||||||
|
var top = top_window.Position[1] + top_window.Size[1] + 10;
|
||||||
|
this.Window.SetPosition(x, top);
|
||||||
|
this.Window.SetSize(width - 2 * 10, 260);
|
||||||
|
|
||||||
|
ResizeInternals(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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(this.gl, thread_name, this, frame_history, this.CheckHandler);
|
||||||
|
this.ThreadRows.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.DrawBackground = function()
|
||||||
|
{
|
||||||
|
// TODO(don): Port all this lot to shader, maybe... it's not performance sensitive
|
||||||
|
|
||||||
|
this.drawContext.clearRect(0, 0, this.drawCanvas.width, this.drawCanvas.height);
|
||||||
|
|
||||||
|
// Draw thread row backgrounds
|
||||||
|
for (let thread_row of this.ThreadRows)
|
||||||
|
{
|
||||||
|
thread_row.DrawBackground(this.HoverSampleInfo, this.yScrollOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timelineMarkers.Draw(this.TimeRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.DrawAllRows = function()
|
||||||
|
{
|
||||||
|
let gl = this.gl;
|
||||||
|
|
||||||
|
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||||
|
|
||||||
|
gl.useProgram(this.Program);
|
||||||
|
|
||||||
|
// Set viewport parameters
|
||||||
|
glSetUniform(gl, this.Program, "inViewport.width", gl.canvas.width);
|
||||||
|
glSetUniform(gl, this.Program, "inViewport.height", gl.canvas.height);
|
||||||
|
|
||||||
|
// Set time range parameters
|
||||||
|
const time_range = this.TimeRange;
|
||||||
|
time_range.SetAsUniform(gl, this.Program);
|
||||||
|
|
||||||
|
// Set text rendering resources
|
||||||
|
// Note it might not be loaded yet so we need the null check
|
||||||
|
if (this.font.atlasTexture != null)
|
||||||
|
{
|
||||||
|
glSetUniform(gl, this.Program, "inFontAtlasTextre", this.font.atlasTexture, 0);
|
||||||
|
this.textBuffer.SetAsUniform(gl, this.Program, "inTextBuffer", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const draw_text = this.Settings.IsPaused;
|
||||||
|
for (let i in this.ThreadRows)
|
||||||
|
{
|
||||||
|
var thread_row = this.ThreadRows[i];
|
||||||
|
thread_row.SetVisibleFrames(time_range);
|
||||||
|
thread_row.Draw(gl, draw_text, this.yScrollOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render last so that each thread row uses any new time ranges
|
||||||
|
this.DrawBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnUserResize(self, evt)
|
||||||
|
{
|
||||||
|
ResizeInternals(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResizeInternals(self)
|
||||||
|
{
|
||||||
|
// .TimelineRowLabel
|
||||||
|
// .TimelineRowExpand
|
||||||
|
// .TimelineRowExpand
|
||||||
|
// .TimelineRowCheck
|
||||||
|
// Window padding
|
||||||
|
let offset_x = 145+19+19+19+10;
|
||||||
|
|
||||||
|
let MarkersHeight = 18;
|
||||||
|
|
||||||
|
var parent_size = self.Window.Size;
|
||||||
|
|
||||||
|
self.timelineMarkers.Resize(BORDER + offset_x, 10, parent_size[0] - 2* BORDER - offset_x, MarkersHeight);
|
||||||
|
|
||||||
|
// Resize controls
|
||||||
|
self.TimelineContainer.SetPosition(BORDER + offset_x, 10 + MarkersHeight);
|
||||||
|
self.TimelineContainer.SetSize(parent_size[0] - 2 * BORDER - offset_x, parent_size[1] - MarkersHeight - 40);
|
||||||
|
|
||||||
|
self.TimelineLabelScrollClipper.SetPosition(10, 10 + MarkersHeight);
|
||||||
|
self.TimelineLabelScrollClipper.SetSize(offset_x, parent_size[1] - MarkersHeight - 40);
|
||||||
|
self.TimelineLabels.SetSize(offset_x, parent_size[1] - MarkersHeight - 40);
|
||||||
|
|
||||||
|
// Match canvas size to container
|
||||||
|
const width = self.TimelineContainer.Node.clientWidth;
|
||||||
|
const height = self.TimelineContainer.Node.clientHeight;
|
||||||
|
self.drawCanvas.width = width;
|
||||||
|
self.drawCanvas.height = height;
|
||||||
|
self.glCanvas.width = width;
|
||||||
|
self.glCanvas.height = height;
|
||||||
|
|
||||||
|
// Adjust time range to new width
|
||||||
|
self.TimeRange.SetPixelSpan(width);
|
||||||
|
self.DrawAllRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseScroll(self, evt)
|
||||||
|
{
|
||||||
|
let mouse_state = new Mouse.State(evt);
|
||||||
|
let scale = 1.11;
|
||||||
|
if (mouse_state.WheelDelta > 0)
|
||||||
|
scale = 1 / scale;
|
||||||
|
|
||||||
|
// What time is the mouse hovering over?
|
||||||
|
let mouse_pos = self.TimelineMousePosition(mouse_state);
|
||||||
|
let time_us = self.TimeRange.TimeAtPosition(mouse_pos[0]);
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
if (self.OnMovedHandler)
|
||||||
|
{
|
||||||
|
self.OnMovedHandler(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent vertical scrolling on mouse-wheel
|
||||||
|
DOM.Event.StopDefaultAction(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.SetTimeRange = function(start_us, span_us)
|
||||||
|
{
|
||||||
|
this.TimeRange.Set(start_us, span_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.DisplayHeight = function()
|
||||||
|
{
|
||||||
|
// Sum height of each thread row
|
||||||
|
let height = 0;
|
||||||
|
for (thread_row of this.ThreadRows)
|
||||||
|
{
|
||||||
|
height += thread_row.DisplayHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.ScrollVertically = function(y_scroll)
|
||||||
|
{
|
||||||
|
// Calculate the minimum negative value the position of the labels can be to account for scrolling to the bottom
|
||||||
|
// of the label/depth list
|
||||||
|
let display_height = this.DisplayHeight();
|
||||||
|
let container_height = this.TimelineLabelScrollClipper.Node.clientHeight;
|
||||||
|
let minimum_y = Math.min(container_height - display_height, 0.0);
|
||||||
|
|
||||||
|
// Resize the label container to match the display height
|
||||||
|
this.TimelineLabels.Node.style.height = Math.max(display_height, container_height);
|
||||||
|
|
||||||
|
// Increment the y-scroll using just-calculated limits
|
||||||
|
let old_y_scroll_offset = this.yScrollOffset;
|
||||||
|
this.yScrollOffset = Math.min(Math.max(this.yScrollOffset + y_scroll, minimum_y), 0);
|
||||||
|
|
||||||
|
// Calculate how much the labels should actually scroll after limiting and apply
|
||||||
|
let y_scroll_px = this.yScrollOffset - old_y_scroll_offset;
|
||||||
|
this.TimelineLabels.Node.style.top = this.TimelineLabels.Node.offsetTop + y_scroll_px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.TimelineMousePosition = function(mouse_state)
|
||||||
|
{
|
||||||
|
// Position of the mouse relative to the timeline container
|
||||||
|
let node_offset = DOM.Node.GetPosition(this.TimelineContainer.Node);
|
||||||
|
let mouse_x = mouse_state.Position[0] - node_offset[0];
|
||||||
|
let mouse_y = mouse_state.Position[1] - node_offset[1];
|
||||||
|
|
||||||
|
// Offset by the amount of scroll
|
||||||
|
mouse_y -= this.yScrollOffset;
|
||||||
|
|
||||||
|
return [ mouse_x, mouse_y ];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TimelineWindow.prototype.GetHoverThreadRow = function(mouse_pos)
|
||||||
|
{
|
||||||
|
// Search for the thread row the mouse intersects
|
||||||
|
let height = 0;
|
||||||
|
for (let thread_row of this.ThreadRows)
|
||||||
|
{
|
||||||
|
let row_height = thread_row.DisplayHeight();
|
||||||
|
if (mouse_pos[1] >= height && mouse_pos[1] < height + row_height)
|
||||||
|
{
|
||||||
|
// Mouse y relative to row start
|
||||||
|
mouse_pos[1] -= height;
|
||||||
|
return thread_row;
|
||||||
|
}
|
||||||
|
height += row_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseDown(self, evt)
|
||||||
|
{
|
||||||
|
// Only manipulate the timeline 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 timeline when paused
|
||||||
|
if (!self.Settings.IsPaused)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var mouse_state = new Mouse.State(evt);
|
||||||
|
|
||||||
|
self.MouseDown = false;
|
||||||
|
|
||||||
|
if (!self.TimelineMoved)
|
||||||
|
{
|
||||||
|
// Are we hovering over a thread row?
|
||||||
|
let mouse_pos = self.TimelineMousePosition(mouse_state);
|
||||||
|
let hover_thread_row = self.GetHoverThreadRow(mouse_pos);
|
||||||
|
if (hover_thread_row != null)
|
||||||
|
{
|
||||||
|
// Are we hovering over a sample?
|
||||||
|
let time_us = self.TimeRange.TimeAtPosition(mouse_pos[0]);
|
||||||
|
let sample_info = hover_thread_row.GetSampleAtPosition(time_us, mouse_pos[1]);
|
||||||
|
if (sample_info != null)
|
||||||
|
{
|
||||||
|
// Redraw with new select sample
|
||||||
|
hover_thread_row.SetSelectSample(sample_info);
|
||||||
|
self.DrawBackground();
|
||||||
|
|
||||||
|
// Call any selection handlers
|
||||||
|
if (self.OnSelectedHandler)
|
||||||
|
{
|
||||||
|
self.OnSelectedHandler(hover_thread_row.Name, sample_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseMove(self, evt)
|
||||||
|
{
|
||||||
|
// Only manipulate the timeline when paused
|
||||||
|
if (!self.Settings.IsPaused)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var mouse_state = new Mouse.State(evt);
|
||||||
|
|
||||||
|
if (self.MouseDown)
|
||||||
|
{
|
||||||
|
let movement = false;
|
||||||
|
|
||||||
|
// Shift the visible time range with mouse movement
|
||||||
|
let time_offset_us = (mouse_state.Position[0] - self.LastMouseState.Position[0]) / self.TimeRange.usPerPixel;
|
||||||
|
if (time_offset_us != 0)
|
||||||
|
{
|
||||||
|
self.TimeRange.SetStart(self.TimeRange.Start_us - time_offset_us);
|
||||||
|
movement = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Control vertical movement
|
||||||
|
let y_offset_px = mouse_state.Position[1] - self.LastMouseState.Position[1];
|
||||||
|
if (y_offset_px != 0)
|
||||||
|
{
|
||||||
|
self.ScrollVertically(y_offset_px);
|
||||||
|
movement = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redraw everything if there is movement
|
||||||
|
if (movement)
|
||||||
|
{
|
||||||
|
self.DrawAllRows();
|
||||||
|
self.TimelineMoved = true;
|
||||||
|
|
||||||
|
if (self.OnMovedHandler)
|
||||||
|
{
|
||||||
|
self.OnMovedHandler(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Are we hovering over a thread row?
|
||||||
|
let mouse_pos = self.TimelineMousePosition(mouse_state);
|
||||||
|
let hover_thread_row = self.GetHoverThreadRow(mouse_pos);
|
||||||
|
if (hover_thread_row != null)
|
||||||
|
{
|
||||||
|
// Are we hovering over a sample?
|
||||||
|
let time_us = self.TimeRange.TimeAtPosition(mouse_pos[0]);
|
||||||
|
self.HoverSampleInfo = hover_thread_row.GetSampleAtPosition(time_us, mouse_pos[1]);
|
||||||
|
|
||||||
|
// Tell listeners which sample we're hovering over
|
||||||
|
if (self.OnHoverHandler != null)
|
||||||
|
{
|
||||||
|
self.OnHoverHandler(hover_thread_row.Name, self.HoverSampleInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.HoverSampleInfo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redraw to update highlights
|
||||||
|
self.DrawBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.LastMouseState = mouse_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseLeave(self, evt)
|
||||||
|
{
|
||||||
|
// Only manipulate the timeline when paused
|
||||||
|
if (!self.Settings.IsPaused)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Cancel scrolling
|
||||||
|
self.MouseDown = false;
|
||||||
|
|
||||||
|
// Cancel hovering
|
||||||
|
if (self.OnHoverHandler != null)
|
||||||
|
{
|
||||||
|
self.OnHoverHandler(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return TimelineWindow;
|
||||||
|
})();
|
||||||
|
|
||||||
105
vis/Code/TitleWindow.js
Normal file
105
vis/Code/TitleWindow.js
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
this.SyncButton = this.Window.AddControlNew(new WM.Button("Sync Timelines", 5, 5, { toggle: true}));
|
||||||
|
this.SyncButton.SetOnClick(Bind(OnSyncPressed, this));
|
||||||
|
this.SyncButton.SetState(this.Settings.SyncTimelines);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleWindow.prototype.Pause = function()
|
||||||
|
{
|
||||||
|
if (!this.Settings.IsPaused)
|
||||||
|
{
|
||||||
|
this.PauseButton.SetText("Paused");
|
||||||
|
this.PauseButton.SetState(true);
|
||||||
|
this.Settings.IsPaused = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleWindow.prototype.Unpause = function()
|
||||||
|
{
|
||||||
|
if (this.Settings.IsPaused)
|
||||||
|
{
|
||||||
|
this.PauseButton.SetText("Pause");
|
||||||
|
this.PauseButton.SetState(false);
|
||||||
|
this.Settings.IsPaused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function OnUserResize(self, evt)
|
||||||
|
{
|
||||||
|
ResizeInternals(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResizeInternals(self)
|
||||||
|
{
|
||||||
|
self.PauseButton.SetPosition(self.Window.Size[0] - 60, 5);
|
||||||
|
self.SyncButton.SetPosition(self.Window.Size[0] - 155, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnPausePressed(self)
|
||||||
|
{
|
||||||
|
if (self.PauseButton.IsPressed())
|
||||||
|
{
|
||||||
|
self.Pause();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.Unpause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnSyncPressed(self)
|
||||||
|
{
|
||||||
|
self.Settings.SyncTimelines = self.SyncButton.IsPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
})();
|
||||||
134
vis/Code/TraceDrop.js
Normal file
134
vis/Code/TraceDrop.js
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
|
||||||
|
class TraceDrop
|
||||||
|
{
|
||||||
|
constructor(remotery)
|
||||||
|
{
|
||||||
|
this.Remotery = remotery;
|
||||||
|
|
||||||
|
// Create a full-page overlay div for dropping files onto
|
||||||
|
this.DropNode = DOM.Node.CreateHTML("<div id='DropZone' class='DropZone'>Load Remotery Trace</div>");
|
||||||
|
document.body.appendChild(this.DropNode);
|
||||||
|
|
||||||
|
// Attach drop handlers
|
||||||
|
window.addEventListener("dragenter", () => this.ShowDropZone());
|
||||||
|
this.DropNode.addEventListener("dragenter", (e) => this.AllowDrag(e));
|
||||||
|
this.DropNode.addEventListener("dragover", (e) => this.AllowDrag(e));
|
||||||
|
this.DropNode.addEventListener("dragleave", () => this.HideDropZone());
|
||||||
|
this.DropNode.addEventListener("drop", (e) => this.OnDrop(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowDropZone()
|
||||||
|
{
|
||||||
|
this.DropNode.style.display = "flex";
|
||||||
|
}
|
||||||
|
|
||||||
|
HideDropZone()
|
||||||
|
{
|
||||||
|
this.DropNode.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
AllowDrag(evt)
|
||||||
|
{
|
||||||
|
// Prevent the default drag handler kicking in
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
evt.dataTransfer.dropEffect = "copy";
|
||||||
|
}
|
||||||
|
|
||||||
|
OnDrop(evt)
|
||||||
|
{
|
||||||
|
// Prevent the default drop handler kicking in
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
this.HideDropZone(evt);
|
||||||
|
|
||||||
|
// Get the file that was dropped
|
||||||
|
let files = DOM.Event.GetDropFiles(evt);
|
||||||
|
if (files.length == 0)
|
||||||
|
{
|
||||||
|
alert("No files dropped");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (files.length > 1)
|
||||||
|
{
|
||||||
|
alert("Too many files dropped");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file type
|
||||||
|
let file = files[0];
|
||||||
|
if (!file.name.endsWith(".rbin"))
|
||||||
|
{
|
||||||
|
alert("Not the correct .rbin file type");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background-load the file
|
||||||
|
var remotery = this.Remotery;
|
||||||
|
let file_reader = new FileReader();
|
||||||
|
file_reader.onload = function()
|
||||||
|
{
|
||||||
|
// Create the data reader and verify the header
|
||||||
|
let data_view = new DataView(this.result);
|
||||||
|
let data_view_reader = new DataViewReader(data_view, 0);
|
||||||
|
let header = data_view_reader.GetStringOfLength(8);
|
||||||
|
if (header != "RMTBLOGF")
|
||||||
|
{
|
||||||
|
alert("Not a valid Remotery Log File");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
remotery.Clear();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Forward all recorded events to message handlers
|
||||||
|
while (!data_view_reader.AtEnd())
|
||||||
|
{
|
||||||
|
remotery.Server.CallMessageHandlers(data_view_reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
// The last message may be partially written due to process exit
|
||||||
|
// Catch this safely as it's a valid state for the file to be in
|
||||||
|
if (e instanceof RangeError)
|
||||||
|
{
|
||||||
|
console.log("Aborted reading last message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After loading completes, populate the UI which wasn't updated during loading
|
||||||
|
|
||||||
|
remotery.Console.TriggerUpdate();
|
||||||
|
|
||||||
|
// Set frame history for each timeline thread
|
||||||
|
for (let name in remotery.FrameHistory)
|
||||||
|
{
|
||||||
|
let frame_history = remotery.FrameHistory[name];
|
||||||
|
remotery.SampleTimelineWindow.OnSamples(name, frame_history);
|
||||||
|
}
|
||||||
|
remotery.SampleTimelineWindow.DrawAllRows();
|
||||||
|
|
||||||
|
for (let name in remotery.ProcessorFrameHistory)
|
||||||
|
{
|
||||||
|
let frame_history = remotery.ProcessorFrameHistory[name];
|
||||||
|
remotery.ProcessorTimelineWindow.OnSamples(name, frame_history);
|
||||||
|
}
|
||||||
|
remotery.ProcessorTimelineWindow.DrawAllRows();
|
||||||
|
|
||||||
|
// Set the last frame of each thread sample history on the sample windows
|
||||||
|
for (let name in remotery.SampleWindows)
|
||||||
|
{
|
||||||
|
let sample_window = remotery.SampleWindows[name];
|
||||||
|
let frame_history = remotery.FrameHistory[name];
|
||||||
|
let frame = frame_history[frame_history.length - 1];
|
||||||
|
sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pause for viewing
|
||||||
|
remotery.TitleWindow.Pause();
|
||||||
|
};
|
||||||
|
file_reader.readAsArrayBuffer(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
238
vis/Code/WebGL.js
Normal file
238
vis/Code/WebGL.js
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
|
||||||
|
function assert(condition, message)
|
||||||
|
{
|
||||||
|
if (!condition)
|
||||||
|
{
|
||||||
|
throw new Error(message || "Assertion failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function glCompileShader(gl, type, name, source)
|
||||||
|
{
|
||||||
|
console.log("Compiling " + name);
|
||||||
|
|
||||||
|
// Compile the shader
|
||||||
|
let shader = gl.createShader(type);
|
||||||
|
gl.shaderSource(shader, source);
|
||||||
|
gl.compileShader(shader);
|
||||||
|
|
||||||
|
// Report any errors
|
||||||
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
|
||||||
|
{
|
||||||
|
console.log("Error compiling " + name);
|
||||||
|
console.log(gl.getShaderInfoLog(shader));
|
||||||
|
console.trace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
function glCreateProgram(gl, vshader, fshader)
|
||||||
|
{
|
||||||
|
// Attach shaders and link
|
||||||
|
let program = gl.createProgram();
|
||||||
|
gl.attachShader(program, vshader);
|
||||||
|
gl.attachShader(program, fshader);
|
||||||
|
gl.linkProgram(program);
|
||||||
|
|
||||||
|
// Report any errors
|
||||||
|
if (!gl.getProgramParameter(program, gl.LINK_STATUS))
|
||||||
|
{
|
||||||
|
console.log("Failed to link program");
|
||||||
|
console.trace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
function glSetUniform(gl, program, name, value, index)
|
||||||
|
{
|
||||||
|
// Get location
|
||||||
|
const location = gl.getUniformLocation(program, name);
|
||||||
|
assert(location != null, "Can't find uniform " + name);
|
||||||
|
|
||||||
|
// Dispatch to uniform function by type
|
||||||
|
assert(value != null, "Value is null");
|
||||||
|
const type = Object.prototype.toString.call(value).slice(8, -1);
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case "Number":
|
||||||
|
gl.uniform1f(location, value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "WebGLTexture":
|
||||||
|
gl.activeTexture(gl.TEXTURE0 + index);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, value);
|
||||||
|
gl.uniform1i(location, index);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false, "Unhandled type " + type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function glCreateTexture(gl, width, height, data)
|
||||||
|
{
|
||||||
|
const texture = gl.createTexture();
|
||||||
|
|
||||||
|
// Set filtering/wrapping to nearest/clamp
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
const glDynamicBufferType = Object.freeze({
|
||||||
|
Buffer: 1,
|
||||||
|
Texture: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
class glDynamicBuffer
|
||||||
|
{
|
||||||
|
constructor(gl, element_type, nb_elements, nb_entries, buffer_type)
|
||||||
|
{
|
||||||
|
this.gl = gl;
|
||||||
|
this.elementType = element_type;
|
||||||
|
this.nbElements = nb_elements;
|
||||||
|
this.bufferType = buffer_type == undefined ? glDynamicBufferType.Buffer : buffer_type;
|
||||||
|
this.dirty = false;
|
||||||
|
|
||||||
|
this.Resize(nb_entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
BindAsInstanceAttribute(program, attrib_name)
|
||||||
|
{
|
||||||
|
assert(this.bufferType == glDynamicBufferType.Buffer, "Can only call BindAsInstanceAttribute with Buffer types");
|
||||||
|
|
||||||
|
let gl = this.gl;
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
||||||
|
|
||||||
|
// The attribute referenced in the program
|
||||||
|
const attrib_location = gl.getAttribLocation(program, attrib_name);
|
||||||
|
|
||||||
|
gl.enableVertexAttribArray(attrib_location);
|
||||||
|
gl.vertexAttribPointer(attrib_location, this.nbElements, this.elementType, false, 0, 0);
|
||||||
|
|
||||||
|
// One per instance
|
||||||
|
gl.vertexAttribDivisor(attrib_location, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
UploadData()
|
||||||
|
{
|
||||||
|
let gl = this.gl;
|
||||||
|
|
||||||
|
switch (this.bufferType)
|
||||||
|
{
|
||||||
|
case glDynamicBufferType.Buffer:
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
||||||
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.cpuArray);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case glDynamicBufferType.Texture:
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, this.nbEntries, 1, 0, gl.ALPHA, gl.UNSIGNED_BYTE, this.cpuArray);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UploadDirtyData()
|
||||||
|
{
|
||||||
|
if (this.dirty)
|
||||||
|
{
|
||||||
|
this.UploadData();
|
||||||
|
this.dirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResizeToFitNextPow2(target_count)
|
||||||
|
{
|
||||||
|
let nb_entries = this.nbEntries;
|
||||||
|
while (target_count > nb_entries)
|
||||||
|
{
|
||||||
|
nb_entries <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nb_entries > this.nbEntries)
|
||||||
|
{
|
||||||
|
this.Resize(nb_entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Resize(nb_entries)
|
||||||
|
{
|
||||||
|
let gl = this.gl;
|
||||||
|
|
||||||
|
this.nbEntries = nb_entries;
|
||||||
|
|
||||||
|
// Create the CPU array
|
||||||
|
const old_array = this.cpuArray;
|
||||||
|
switch (this.elementType)
|
||||||
|
{
|
||||||
|
case gl.FLOAT:
|
||||||
|
this.nbElementBytes = 4;
|
||||||
|
this.cpuArray = new Float32Array(this.nbElements * this.nbEntries);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case gl.BYTE:
|
||||||
|
this.nbElementBytes = 1;
|
||||||
|
this.cpuArray = new Uint8Array(this.nbElements * this.nbEntries);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false, "Unsupported dynamic buffer element type");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate byte size of the buffer
|
||||||
|
this.nbBytes = this.nbElementBytes * this.nbElements * this.nbEntries;
|
||||||
|
|
||||||
|
if (old_array != undefined)
|
||||||
|
{
|
||||||
|
// Copy the values of the previous array over
|
||||||
|
this.cpuArray.set(old_array);
|
||||||
|
|
||||||
|
console.log(`glDynamicBuffer: Resizing to ${nb_entries} entries, ${this.nbElements} elements per entry, ${this.nbElementBytes} bytes per element, ${this.nbBytes} bytes total.`);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log(`glDynamicBuffer: Creating ${nb_entries} entries, ${this.nbElements} elements per entry, ${this.nbElementBytes} bytes per element, ${this.nbBytes} bytes total.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the GPU buffer
|
||||||
|
switch (this.bufferType)
|
||||||
|
{
|
||||||
|
case glDynamicBufferType.Buffer:
|
||||||
|
this.buffer = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, this.nbBytes, gl.DYNAMIC_DRAW);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case glDynamicBufferType.Texture:
|
||||||
|
this.texture = gl.createTexture();
|
||||||
|
|
||||||
|
// Point sampling with clamp for indexing
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
// Fixed-format for now
|
||||||
|
assert(this.elementType == gl.BYTE);
|
||||||
|
assert(this.nbElements == 1);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, this.nbEntries, 1, 0, gl.ALPHA, gl.UNSIGNED_BYTE, this.cpuArray);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false, "Unsupported dynamic buffer type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
119
vis/Code/WebGLFont.js
Normal file
119
vis/Code/WebGLFont.js
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
|
||||||
|
class glFont
|
||||||
|
{
|
||||||
|
constructor(gl)
|
||||||
|
{
|
||||||
|
// Offscreen canvas for rendering individual characters
|
||||||
|
this.charCanvas = document.createElement("canvas");
|
||||||
|
this.charContext = this.charCanvas.getContext("2d");
|
||||||
|
|
||||||
|
// Describe the font
|
||||||
|
const font_size = 9;
|
||||||
|
this.fontWidth = 5;
|
||||||
|
this.fontHeight = 12;
|
||||||
|
const font_face = "LocalFiraCode";
|
||||||
|
const font_desc = font_size + "px " + font_face;
|
||||||
|
|
||||||
|
// Ensure the CSS font is loaded before we do any work with it
|
||||||
|
const self = this;
|
||||||
|
document.fonts.load(font_desc).then(function (){
|
||||||
|
|
||||||
|
// Create a canvas atlas for all characters in the font
|
||||||
|
const atlas_canvas = document.createElement("canvas");
|
||||||
|
const atlas_context = atlas_canvas.getContext("2d");
|
||||||
|
atlas_canvas.width = 16 * self.fontWidth;
|
||||||
|
atlas_canvas.height = 16 * self.fontHeight;
|
||||||
|
|
||||||
|
// Add each character to the atlas
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-+=[]{};\'~#,./<>?!\"£$%%^&*()";
|
||||||
|
for (let char of chars)
|
||||||
|
{
|
||||||
|
// Render this character to the canvas on its own
|
||||||
|
self.RenderTextToCanvas(char, font_desc, self.fontWidth, self.fontHeight);
|
||||||
|
|
||||||
|
// Calculate a location for it in the atlas using its ASCII code
|
||||||
|
const ascii_code = char.charCodeAt(0);
|
||||||
|
assert(ascii_code < 256);
|
||||||
|
const y_index = Math.floor(ascii_code / 16);
|
||||||
|
const x_index = ascii_code - y_index * 16
|
||||||
|
assert(x_index < 16);
|
||||||
|
assert(y_index < 16);
|
||||||
|
|
||||||
|
// Copy into the atlas
|
||||||
|
atlas_context.drawImage(self.charCanvas, x_index * self.fontWidth, y_index * self.fontHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the atlas texture and store it in the destination object
|
||||||
|
self.atlasTexture = glCreateTexture(gl, atlas_canvas.width, atlas_canvas.height, atlas_canvas);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTextToCanvas(text, font, width, height)
|
||||||
|
{
|
||||||
|
// Resize canvas to match
|
||||||
|
this.charCanvas.width = width;
|
||||||
|
this.charCanvas.height = height;
|
||||||
|
|
||||||
|
// Clear the background
|
||||||
|
this.charContext.fillStyle = "black";
|
||||||
|
this.charContext.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// Render the text
|
||||||
|
this.charContext.font = font;
|
||||||
|
this.charContext.textAlign = "left";
|
||||||
|
this.charContext.textBaseline = "top";
|
||||||
|
this.charContext.fillText(text, 0, 1.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class glTextBuffer
|
||||||
|
{
|
||||||
|
constructor(gl, font)
|
||||||
|
{
|
||||||
|
this.font = font;
|
||||||
|
this.textMap = {};
|
||||||
|
this.textBuffer = new glDynamicBuffer(gl, gl.BYTE, 1, 8, glDynamicBufferType.Texture);
|
||||||
|
this.textBufferPos = 0;
|
||||||
|
this.textEncoder = new TextEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
AddText(text)
|
||||||
|
{
|
||||||
|
// Return if it already exists
|
||||||
|
const existing_entry = this.textMap[text];
|
||||||
|
if (existing_entry != undefined)
|
||||||
|
{
|
||||||
|
return existing_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the map
|
||||||
|
// Note we're leaving an extra NULL character before every piece of text so that the shader can sample into it on text
|
||||||
|
// boundaries and sample a zero colour for clamp.
|
||||||
|
let entry = {
|
||||||
|
offset: this.textBufferPos + 1,
|
||||||
|
length: text.length,
|
||||||
|
};
|
||||||
|
this.textMap[text] = entry;
|
||||||
|
|
||||||
|
// Ensure there's always enough space in the text buffer before adding
|
||||||
|
this.textBuffer.ResizeToFitNextPow2(entry.offset + entry.length + 1);
|
||||||
|
this.textBuffer.cpuArray.set(this.textEncoder.encode(text), entry.offset, entry.length);
|
||||||
|
this.textBuffer.dirty = true;
|
||||||
|
this.textBufferPos = entry.offset + entry.length;
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
UploadData()
|
||||||
|
{
|
||||||
|
this.textBuffer.UploadDirtyData();
|
||||||
|
}
|
||||||
|
|
||||||
|
SetAsUniform(gl, program, name, index)
|
||||||
|
{
|
||||||
|
glSetUniform(gl, program, name, this.textBuffer.texture, index);
|
||||||
|
glSetUniform(gl, program, "inTextBufferDesc.fontWidth", this.font.fontWidth);
|
||||||
|
glSetUniform(gl, program, "inTextBufferDesc.fontHeight", this.font.fontHeight);
|
||||||
|
glSetUniform(gl, program, "inTextBufferDesc.textBufferLength", this.textBuffer.nbEntries);
|
||||||
|
}
|
||||||
|
}
|
||||||
137
vis/Code/WebSocketConnection.js
Normal file
137
vis/Code/WebSocketConnection.js
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
let data_view = new DataView(event.data);
|
||||||
|
let data_view_reader = new DataViewReader(data_view, 0);
|
||||||
|
self.CallMessageHandlers(data_view_reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketConnection.prototype.CallMessageHandlers = function(data_view_reader)
|
||||||
|
{
|
||||||
|
let id = data_view_reader.GetStringOfLength(4);
|
||||||
|
CallMessageHandlers(this, id, data_view_reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return WebSocketConnection;
|
||||||
|
})();
|
||||||
BIN
vis/Styles/Fonts/FiraCode/FiraCode-Regular.ttf
Normal file
BIN
vis/Styles/Fonts/FiraCode/FiraCode-Regular.ttf
Normal file
Binary file not shown.
93
vis/Styles/Fonts/FiraCode/LICENSE
Normal file
93
vis/Styles/Fonts/FiraCode/LICENSE
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
Copyright (c) 2014, The Fira Code Project Authors (https://github.com/tonsky/FiraCode)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
274
vis/Styles/Remotery.css
Normal file
274
vis/Styles/Remotery.css
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@font-face
|
||||||
|
{
|
||||||
|
font-family: "LocalFiraCode";
|
||||||
|
src:url("Fonts/FiraCode/FiraCode-Regular.ttf");
|
||||||
|
}
|
||||||
|
|
||||||
|
.ConsoleText
|
||||||
|
{
|
||||||
|
overflow:auto;
|
||||||
|
color: #BBB;
|
||||||
|
font: 10px LocalFiraCode;
|
||||||
|
margin: 3px;
|
||||||
|
white-space: pre;
|
||||||
|
line-height:14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.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
|
||||||
|
{
|
||||||
|
float:left;
|
||||||
|
|
||||||
|
width: 140px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TimelineContainer
|
||||||
|
{
|
||||||
|
}
|
||||||
|
.TimelineLabels
|
||||||
|
{
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
.TimelineLabelScrollClipper
|
||||||
|
{
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DropZone
|
||||||
|
{
|
||||||
|
/* Covers the whole page, initially hidden */
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
/* On top of everything possible */
|
||||||
|
z-index: 99999;
|
||||||
|
|
||||||
|
/* Styling for when visible */
|
||||||
|
background: rgba(32, 4, 136, 0.25);
|
||||||
|
border: 3px dashed white;
|
||||||
|
|
||||||
|
/* Styling for text when visible */
|
||||||
|
color: white;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: xx-large;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
65
vis/extern/BrowserLib/Core/Code/Animation.js
vendored
Normal file
65
vis/extern/BrowserLib/Core/Code/Animation.js
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
92
vis/extern/BrowserLib/Core/Code/Bind.js
vendored
Normal file
92
vis/extern/BrowserLib/Core/Code/Bind.js
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
//
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
218
vis/extern/BrowserLib/Core/Code/Convert.js
vendored
Normal file
218
vis/extern/BrowserLib/Core/Code/Convert.js
vendored
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
|
||||||
|
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) + ';';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
26
vis/extern/BrowserLib/Core/Code/Core.js
vendored
Normal file
26
vis/extern/BrowserLib/Core/Code/Core.js
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
526
vis/extern/BrowserLib/Core/Code/DOM.js
vendored
Normal file
526
vis/extern/BrowserLib/Core/Code/DOM.js
vendored
Normal file
|
|
@ -0,0 +1,526 @@
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Get the list of files attached to a drop event
|
||||||
|
//
|
||||||
|
DOM.Event.GetDropFiles = function(evt)
|
||||||
|
{
|
||||||
|
let files = [];
|
||||||
|
if (evt.dataTransfer.items)
|
||||||
|
{
|
||||||
|
for (let i = 0; i < evt.dataTransfer.items.length; i++)
|
||||||
|
{
|
||||||
|
if (evt.dataTransfer.items[i].kind === 'file')
|
||||||
|
{
|
||||||
|
files.push(evt.dataTransfer.items[i].getAsFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (let i = 0; i < evt.dataTransfer.files.length; i++)
|
||||||
|
{
|
||||||
|
files.push(evt.dataTransfer.files[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// =====================================================================================================================
|
||||||
|
// ----- 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";
|
||||||
|
}
|
||||||
149
vis/extern/BrowserLib/Core/Code/Keyboard.js
vendored
Normal file
149
vis/extern/BrowserLib/Core/Code/Keyboard.js
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
// =====================================================================================================================
|
||||||
40
vis/extern/BrowserLib/Core/Code/LocalStore.js
vendored
Normal file
40
vis/extern/BrowserLib/Core/Code/LocalStore.js
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
83
vis/extern/BrowserLib/Core/Code/Mouse.js
vendored
Normal file
83
vis/extern/BrowserLib/Core/Code/Mouse.js
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
68
vis/extern/BrowserLib/Core/Code/MurmurHash3.js
vendored
Normal file
68
vis/extern/BrowserLib/Core/Code/MurmurHash3.js
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
131
vis/extern/BrowserLib/WindowManager/Code/Button.js
vendored
Normal file
131
vis/extern/BrowserLib/WindowManager/Code/Button.js
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
|
||||||
|
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;
|
||||||
|
})();
|
||||||
237
vis/extern/BrowserLib/WindowManager/Code/ComboBox.js
vendored
Normal file
237
vis/extern/BrowserLib/WindowManager/Code/ComboBox.js
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
|
||||||
|
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;
|
||||||
|
})();
|
||||||
48
vis/extern/BrowserLib/WindowManager/Code/Container.js
vendored
Normal file
48
vis/extern/BrowserLib/WindowManager/Code/Container.js
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Container.prototype.AddControlNew = function(control)
|
||||||
|
{
|
||||||
|
control.ParentNode = this.Node;
|
||||||
|
this.Node.appendChild(control.Node);
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Container.prototype.ClearControls = function()
|
||||||
|
{
|
||||||
|
this.Node.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return Container;
|
||||||
|
})();
|
||||||
119
vis/extern/BrowserLib/WindowManager/Code/EditBox.js
vendored
Normal file
119
vis/extern/BrowserLib/WindowManager/Code/EditBox.js
vendored
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
|
||||||
|
namespace("WM");
|
||||||
|
|
||||||
|
|
||||||
|
WM.EditBox = (function()
|
||||||
|
{
|
||||||
|
var template_html = " \
|
||||||
|
<div class='EditBoxContainer'> \
|
||||||
|
<div class='EditBoxLabel'>Label</div> \
|
||||||
|
<input class='EditBox'> \
|
||||||
|
</div>";
|
||||||
|
|
||||||
|
|
||||||
|
function EditBox(x, y, w, h, label, text)
|
||||||
|
{
|
||||||
|
this.ChangeHandler = null;
|
||||||
|
|
||||||
|
// Create node and locate its internal nodes
|
||||||
|
this.Node = DOM.Node.CreateHTML(template_html);
|
||||||
|
this.LabelNode = DOM.Node.FindWithClass(this.Node, "EditBoxLabel");
|
||||||
|
this.EditNode = DOM.Node.FindWithClass(this.Node, "EditBox");
|
||||||
|
|
||||||
|
// Set label and value
|
||||||
|
this.LabelNode.innerHTML = label;
|
||||||
|
this.SetValue(text);
|
||||||
|
|
||||||
|
this.SetPosition(x, y);
|
||||||
|
this.SetSize(w, h);
|
||||||
|
|
||||||
|
this.PreviousValue = "";
|
||||||
|
|
||||||
|
// Hook up the event handlers
|
||||||
|
DOM.Event.AddHandler(this.EditNode, "focus", Bind(OnFocus, this));
|
||||||
|
DOM.Event.AddHandler(this.EditNode, "keypress", Bind(OnKeyPress, this));
|
||||||
|
DOM.Event.AddHandler(this.EditNode, "keydown", Bind(OnKeyDown, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EditBox.prototype.SetPosition = function(x, y)
|
||||||
|
{
|
||||||
|
this.Position = [ x, y ];
|
||||||
|
DOM.Node.SetPosition(this.Node, this.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EditBox.prototype.SetSize = function(w, h)
|
||||||
|
{
|
||||||
|
this.Size = [ w, h ];
|
||||||
|
DOM.Node.SetSize(this.EditNode, this.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EditBox.prototype.SetChangeHandler = function(handler)
|
||||||
|
{
|
||||||
|
this.ChangeHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EditBox.prototype.SetValue = function(value)
|
||||||
|
{
|
||||||
|
if (this.EditNode)
|
||||||
|
this.EditNode.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EditBox.prototype.GetValue = function()
|
||||||
|
{
|
||||||
|
if (this.EditNode)
|
||||||
|
return this.EditNode.value;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EditBox.prototype.LoseFocus = function()
|
||||||
|
{
|
||||||
|
if (this.EditNode)
|
||||||
|
this.EditNode.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnFocus(self, evt)
|
||||||
|
{
|
||||||
|
// Backup on focus
|
||||||
|
self.PreviousValue = self.EditNode.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnKeyPress(self, evt)
|
||||||
|
{
|
||||||
|
// Allow enter to confirm the text only when there's data
|
||||||
|
if (evt.keyCode == 13 && self.EditNode.value != "" && self.ChangeHandler)
|
||||||
|
{
|
||||||
|
var focus = self.ChangeHandler(self.EditNode);
|
||||||
|
if (!focus)
|
||||||
|
self.EditNode.blur();
|
||||||
|
self.PreviousValue = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnKeyDown(self, evt)
|
||||||
|
{
|
||||||
|
// Allow escape to cancel any text changes
|
||||||
|
if (evt.keyCode == 27)
|
||||||
|
{
|
||||||
|
// On initial edit of the input, escape should NOT replace with the empty string
|
||||||
|
if (self.PreviousValue != "")
|
||||||
|
{
|
||||||
|
self.EditNode.value = self.PreviousValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.EditNode.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return EditBox;
|
||||||
|
})();
|
||||||
248
vis/extern/BrowserLib/WindowManager/Code/Grid.js
vendored
Normal file
248
vis/extern/BrowserLib/WindowManager/Code/Grid.js
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
|
||||||
|
namespace("WM");
|
||||||
|
|
||||||
|
|
||||||
|
WM.GridRows = (function()
|
||||||
|
{
|
||||||
|
function GridRows(parent_object)
|
||||||
|
{
|
||||||
|
this.ParentObject = parent_object;
|
||||||
|
|
||||||
|
// Array of rows in the order they were added
|
||||||
|
this.Rows = [ ];
|
||||||
|
|
||||||
|
// Collection of custom row indexes for fast lookup
|
||||||
|
this.Indexes = { };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GridRows.prototype.AddIndex = function(cell_field_name)
|
||||||
|
{
|
||||||
|
var index = { };
|
||||||
|
|
||||||
|
// Go through existing rows and add to the index
|
||||||
|
for (var i in this.Rows)
|
||||||
|
{
|
||||||
|
var row = this.Rows[i];
|
||||||
|
if (cell_field_name in row.CellData)
|
||||||
|
{
|
||||||
|
var cell_field = row.CellData[cell_field_name];
|
||||||
|
index[cell_field] = row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Indexes[cell_field_name] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GridRows.prototype.ClearIndex = function(index_name)
|
||||||
|
{
|
||||||
|
this.Indexes[index_name] = { };
|
||||||
|
}
|
||||||
|
|
||||||
|
GridRows.prototype.AddRowToIndex = function(index_name, cell_data, row)
|
||||||
|
{
|
||||||
|
this.Indexes[index_name][cell_data] = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GridRows.prototype.Add = function(cell_data, row_classes, cell_classes)
|
||||||
|
{
|
||||||
|
var row = new WM.GridRow(this.ParentObject, cell_data, row_classes, cell_classes);
|
||||||
|
this.Rows.push(row);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GridRows.prototype.GetBy = function(cell_field_name, cell_data)
|
||||||
|
{
|
||||||
|
var index = this.Indexes[cell_field_name];
|
||||||
|
return index[cell_data];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GridRows.prototype.Clear = function()
|
||||||
|
{
|
||||||
|
// Remove all node references from the parent
|
||||||
|
for (var i in this.Rows)
|
||||||
|
{
|
||||||
|
var row = this.Rows[i];
|
||||||
|
row.Parent.BodyNode.removeChild(row.Node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all indexes
|
||||||
|
for (var i in this.Indexes)
|
||||||
|
this.Indexes[i] = { };
|
||||||
|
|
||||||
|
this.Rows = [ ];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return GridRows;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
WM.GridRow = (function()
|
||||||
|
{
|
||||||
|
var template_html = "<div class='GridRow'></div>";
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// 'cell_data' is an object with a variable number of fields.
|
||||||
|
// Any fields prefixed with an underscore are hidden.
|
||||||
|
//
|
||||||
|
function GridRow(parent, cell_data, row_classes, cell_classes)
|
||||||
|
{
|
||||||
|
// Setup data
|
||||||
|
this.Parent = parent;
|
||||||
|
this.IsOpen = true;
|
||||||
|
this.AnimHandle = null;
|
||||||
|
this.Rows = new WM.GridRows(this);
|
||||||
|
this.CellData = cell_data;
|
||||||
|
this.CellNodes = { }
|
||||||
|
|
||||||
|
// Create the main row node
|
||||||
|
this.Node = DOM.Node.CreateHTML(template_html);
|
||||||
|
if (row_classes)
|
||||||
|
DOM.Node.AddClass(this.Node, row_classes);
|
||||||
|
|
||||||
|
// Embed a pointer to the row in the root node so that it can be clicked
|
||||||
|
this.Node.GridRow = this;
|
||||||
|
|
||||||
|
// Create nodes for each required cell
|
||||||
|
for (var attr in this.CellData)
|
||||||
|
{
|
||||||
|
if (this.CellData.hasOwnProperty(attr))
|
||||||
|
{
|
||||||
|
var data = this.CellData[attr];
|
||||||
|
|
||||||
|
// Update any grid row index references
|
||||||
|
if (attr in parent.Rows.Indexes)
|
||||||
|
parent.Rows.AddRowToIndex(attr, data, this);
|
||||||
|
|
||||||
|
// Hide any cells with underscore prefixes
|
||||||
|
if (attr[0] == "_")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Create a node for the cell and add any custom classes
|
||||||
|
var node = DOM.Node.AppendHTML(this.Node, "<div class='GridRowCell'></div>");
|
||||||
|
if (cell_classes && attr in cell_classes)
|
||||||
|
DOM.Node.AddClass(node, cell_classes[attr]);
|
||||||
|
this.CellNodes[attr] = node;
|
||||||
|
|
||||||
|
// If this is a Window Control, add its node to the cell
|
||||||
|
if (data instanceof Object && "Node" in data && DOM.Node.IsNode(data.Node))
|
||||||
|
{
|
||||||
|
data.ParentNode = node;
|
||||||
|
node.appendChild(data.Node);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Otherwise just assign the data as text
|
||||||
|
node.innerHTML = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the body node for any children
|
||||||
|
if (!this.Parent.BodyNode)
|
||||||
|
this.Parent.BodyNode = DOM.Node.AppendHTML(this.Parent.Node, "<div class='GridRowBody'></div>");
|
||||||
|
|
||||||
|
// Add the row to the parent
|
||||||
|
this.Parent.BodyNode.appendChild(this.Node);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GridRow.prototype.Open = function()
|
||||||
|
{
|
||||||
|
// Don't allow open while animating
|
||||||
|
if (this.AnimHandle == null || this.AnimHandle.Complete)
|
||||||
|
{
|
||||||
|
this.IsOpen = true;
|
||||||
|
|
||||||
|
// Kick off open animation
|
||||||
|
var node = this.BodyNode;
|
||||||
|
this.AnimHandle = Anim.Animate(
|
||||||
|
function (val) { DOM.Node.SetHeight(node, val) },
|
||||||
|
0, this.Height, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GridRow.prototype.Close = function()
|
||||||
|
{
|
||||||
|
// Don't allow close while animating
|
||||||
|
if (this.AnimHandle == null || this.AnimHandle.Complete)
|
||||||
|
{
|
||||||
|
this.IsOpen = false;
|
||||||
|
|
||||||
|
// Record height for the next open request
|
||||||
|
this.Height = this.BodyNode.offsetHeight;
|
||||||
|
|
||||||
|
// Kick off close animation
|
||||||
|
var node = this.BodyNode;
|
||||||
|
this.AnimHandle = Anim.Animate(
|
||||||
|
function (val) { DOM.Node.SetHeight(node, val) },
|
||||||
|
this.Height, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GridRow.prototype.Toggle = function()
|
||||||
|
{
|
||||||
|
if (this.IsOpen)
|
||||||
|
this.Close();
|
||||||
|
else
|
||||||
|
this.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return GridRow;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
WM.Grid = (function()
|
||||||
|
{
|
||||||
|
var template_html = " \
|
||||||
|
<div class='Grid'> \
|
||||||
|
<div class='GridBody'></div> \
|
||||||
|
</div>";
|
||||||
|
|
||||||
|
|
||||||
|
function Grid()
|
||||||
|
{
|
||||||
|
this.Rows = new WM.GridRows(this);
|
||||||
|
|
||||||
|
this.Node = DOM.Node.CreateHTML(template_html);
|
||||||
|
this.BodyNode = DOM.Node.FindWithClass(this.Node, "GridBody");
|
||||||
|
|
||||||
|
DOM.Event.AddHandler(this.Node, "dblclick", OnDblClick);
|
||||||
|
|
||||||
|
var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel";
|
||||||
|
DOM.Event.AddHandler(this.Node, mouse_wheel_event, Bind(OnMouseScroll, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
function OnDblClick(evt)
|
||||||
|
{
|
||||||
|
// Clicked on a header?
|
||||||
|
var node = DOM.Event.GetNode(evt);
|
||||||
|
if (DOM.Node.HasClass(node, "GridRowName"))
|
||||||
|
{
|
||||||
|
// Toggle rows open/close
|
||||||
|
var row = node.parentNode.GridRow;
|
||||||
|
if (row)
|
||||||
|
row.Toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseScroll(self, evt)
|
||||||
|
{
|
||||||
|
var mouse_state = new Mouse.State(evt);
|
||||||
|
self.Node.scrollTop -= mouse_state.WheelDelta * 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return Grid;
|
||||||
|
})();
|
||||||
31
vis/extern/BrowserLib/WindowManager/Code/Label.js
vendored
Normal file
31
vis/extern/BrowserLib/WindowManager/Code/Label.js
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
namespace("WM");
|
||||||
|
|
||||||
|
|
||||||
|
WM.Label = (function()
|
||||||
|
{
|
||||||
|
var template_html = "<div class='Label'></div>";
|
||||||
|
|
||||||
|
|
||||||
|
function Label(x, y, text)
|
||||||
|
{
|
||||||
|
// Create the node
|
||||||
|
this.Node = DOM.Node.CreateHTML(template_html);
|
||||||
|
|
||||||
|
// Allow position to be optional
|
||||||
|
if (x != null && y != null)
|
||||||
|
DOM.Node.SetPosition(this.Node, [x, y]);
|
||||||
|
|
||||||
|
this.SetText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Label.prototype.SetText = function(text)
|
||||||
|
{
|
||||||
|
if (text != null)
|
||||||
|
this.Node.innerHTML = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return Label;
|
||||||
|
})();
|
||||||
352
vis/extern/BrowserLib/WindowManager/Code/Treeview.js
vendored
Normal file
352
vis/extern/BrowserLib/WindowManager/Code/Treeview.js
vendored
Normal file
|
|
@ -0,0 +1,352 @@
|
||||||
|
|
||||||
|
namespace("WM");
|
||||||
|
|
||||||
|
|
||||||
|
WM.Treeview = (function()
|
||||||
|
{
|
||||||
|
var Margin = 10;
|
||||||
|
|
||||||
|
|
||||||
|
var tree_template_html = " \
|
||||||
|
<div class='Treeview'> \
|
||||||
|
<div class='TreeviewItemChildren' style='width:90%;float:left'></div> \
|
||||||
|
<div class='TreeviewScrollbarInset'> \
|
||||||
|
<div class='TreeviewScrollbar'></div> \
|
||||||
|
</div> \
|
||||||
|
<div style='clear:both'></div> \
|
||||||
|
</div>";
|
||||||
|
|
||||||
|
|
||||||
|
var item_template_html = " \
|
||||||
|
<div class='TreeViewItem basicfont notextsel'> \
|
||||||
|
<img src='' class='TreeviewItemImage'> \
|
||||||
|
<div class='TreeviewItemText'></div> \
|
||||||
|
<div style='clear:both'></div> \
|
||||||
|
<div class='TreeviewItemChildren'></div> \
|
||||||
|
<div style='clear:both'></div> \
|
||||||
|
</div>";
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Remove parent_node (required for stuff that doesn't use the WM yet)
|
||||||
|
function Treeview(x, y, width, height, parent_node)
|
||||||
|
{
|
||||||
|
// Cache initialisation options
|
||||||
|
this.ParentNode = parent_node;
|
||||||
|
this.Position = [ x, y ];
|
||||||
|
this.Size = [ width, height ];
|
||||||
|
|
||||||
|
this.Node = null;
|
||||||
|
this.ScrollbarNode = null;
|
||||||
|
this.SelectedItem = null;
|
||||||
|
this.ContentsNode = null;
|
||||||
|
|
||||||
|
// Setup options
|
||||||
|
this.HighlightOnHover = false;
|
||||||
|
this.EnableScrollbar = true;
|
||||||
|
this.HorizontalLayoutDepth = 1;
|
||||||
|
|
||||||
|
// Generate an empty tree
|
||||||
|
this.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Treeview.prototype.SetHighlightOnHover = function(highlight)
|
||||||
|
{
|
||||||
|
this.HighlightOnHover = highlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Treeview.prototype.SetEnableScrollbar = function(enable)
|
||||||
|
{
|
||||||
|
this.EnableScrollbar = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Treeview.prototype.SetHorizontalLayoutDepth = function(depth)
|
||||||
|
{
|
||||||
|
this.HorizontalLayoutDepth = depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Treeview.prototype.SetNodeSelectedHandler = function(handler)
|
||||||
|
{
|
||||||
|
this.NodeSelectedHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Treeview.prototype.Clear = function()
|
||||||
|
{
|
||||||
|
this.RootItem = new WM.TreeviewItem(this, null, null, null, null);
|
||||||
|
this.GenerateHTML();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Treeview.prototype.Root = function()
|
||||||
|
{
|
||||||
|
return this.RootItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Treeview.prototype.ClearSelection = function()
|
||||||
|
{
|
||||||
|
if (this.SelectedItem != null)
|
||||||
|
{
|
||||||
|
DOM.Node.RemoveClass(this.SelectedItem.Node, "TreeviewItemSelected");
|
||||||
|
this.SelectedItem = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Treeview.prototype.SelectItem = function(item, mouse_pos)
|
||||||
|
{
|
||||||
|
// Notify the select handler
|
||||||
|
if (this.NodeSelectedHandler)
|
||||||
|
this.NodeSelectedHandler(item.Node, this.SelectedItem, item, mouse_pos);
|
||||||
|
|
||||||
|
// Remove highlight from the old selection
|
||||||
|
this.ClearSelection();
|
||||||
|
|
||||||
|
// Swap in new selection and apply highlight
|
||||||
|
this.SelectedItem = item;
|
||||||
|
DOM.Node.AddClass(this.SelectedItem.Node, "TreeviewItemSelected");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Treeview.prototype.GenerateHTML = function()
|
||||||
|
{
|
||||||
|
// Clone the template and locate important nodes
|
||||||
|
var old_node = this.Node;
|
||||||
|
this.Node = DOM.Node.CreateHTML(tree_template_html);
|
||||||
|
this.ChildrenNode = DOM.Node.FindWithClass(this.Node, "TreeviewItemChildren");
|
||||||
|
this.ScrollbarNode = DOM.Node.FindWithClass(this.Node, "TreeviewScrollbar");
|
||||||
|
|
||||||
|
DOM.Node.SetPosition(this.Node, this.Position);
|
||||||
|
DOM.Node.SetSize(this.Node, this.Size);
|
||||||
|
|
||||||
|
// Generate the contents of the treeview
|
||||||
|
GenerateTree(this, this.ChildrenNode, this.RootItem.Children, 0);
|
||||||
|
|
||||||
|
// Cross-browser (?) means of adding a mouse wheel handler
|
||||||
|
var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel";
|
||||||
|
DOM.Event.AddHandler(this.Node, mouse_wheel_event, Bind(OnMouseScroll, this));
|
||||||
|
|
||||||
|
DOM.Event.AddHandler(this.Node, "dblclick", Bind(OnMouseDoubleClick, this));
|
||||||
|
DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this));
|
||||||
|
DOM.Event.AddHandler(this.Node, "mouseup", OnMouseUp);
|
||||||
|
|
||||||
|
// Swap in the newly generated control node if it's already been attached to a parent
|
||||||
|
if (old_node && old_node.parentNode)
|
||||||
|
{
|
||||||
|
old_node.parentNode.removeChild(old_node);
|
||||||
|
this.ParentNode.appendChild(this.Node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.EnableScrollbar)
|
||||||
|
{
|
||||||
|
this.UpdateScrollbar();
|
||||||
|
DOM.Event.AddHandler(this.ScrollbarNode, "mousedown", Bind(OnMouseDown_Scrollbar, this));
|
||||||
|
DOM.Event.AddHandler(this.ScrollbarNode, "mouseup", Bind(OnMouseUp_Scrollbar, this));
|
||||||
|
DOM.Event.AddHandler(this.ScrollbarNode, "mouseout", Bind(OnMouseUp_Scrollbar, this));
|
||||||
|
DOM.Event.AddHandler(this.ScrollbarNode, "mousemove", Bind(OnMouseMove_Scrollbar, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DOM.Node.Hide(DOM.Node.FindWithClass(this.Node, "TreeviewScrollbarInset"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Treeview.prototype.UpdateScrollbar = function()
|
||||||
|
{
|
||||||
|
if (!this.EnableScrollbar)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var scrollbar_scale = Math.min((this.Node.offsetHeight - Margin * 2) / this.ChildrenNode.offsetHeight, 1);
|
||||||
|
this.ScrollbarNode.style.height = parseInt(scrollbar_scale * 100) + "%";
|
||||||
|
|
||||||
|
// Shift the scrollbar container along with the parent window
|
||||||
|
this.ScrollbarNode.parentNode.style.top = this.Node.scrollTop;
|
||||||
|
|
||||||
|
var scroll_fraction = this.Node.scrollTop / (this.Node.scrollHeight - this.Node.offsetHeight);
|
||||||
|
var max_height = this.Node.offsetHeight - Margin;
|
||||||
|
var max_scrollbar_offset = max_height - this.ScrollbarNode.offsetHeight;
|
||||||
|
var scrollbar_offset = scroll_fraction * max_scrollbar_offset;
|
||||||
|
this.ScrollbarNode.style.top = scrollbar_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GenerateTree(self, parent_node, items, depth)
|
||||||
|
{
|
||||||
|
if (items.length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for (var i in items)
|
||||||
|
{
|
||||||
|
var item = items[i];
|
||||||
|
|
||||||
|
// Create the node for this item and locate important nodes
|
||||||
|
var node = DOM.Node.CreateHTML(item_template_html);
|
||||||
|
var img = DOM.Node.FindWithClass(node, "TreeviewItemImage");
|
||||||
|
var text = DOM.Node.FindWithClass(node, "TreeviewItemText");
|
||||||
|
var children = DOM.Node.FindWithClass(node, "TreeviewItemChildren");
|
||||||
|
|
||||||
|
// Attach the item to the node
|
||||||
|
node.TreeviewItem = item;
|
||||||
|
item.Node = node;
|
||||||
|
|
||||||
|
// Add the class which highlights selection on hover
|
||||||
|
if (self.HighlightOnHover)
|
||||||
|
DOM.Node.AddClass(node, "TreeviewItemHover");
|
||||||
|
|
||||||
|
// Instruct the children to wrap around
|
||||||
|
if (depth >= self.HorizontalLayoutDepth)
|
||||||
|
node.style.cssFloat = "left";
|
||||||
|
|
||||||
|
if (item.OpenImage == null || item.CloseImage == null)
|
||||||
|
{
|
||||||
|
// If there no images, remove the image node
|
||||||
|
node.removeChild(img);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Set the image source to open
|
||||||
|
img.src = item.OpenImage.src;
|
||||||
|
img.style.width = item.OpenImage.width;
|
||||||
|
img.style.height = item.OpenImage.height;
|
||||||
|
item.ImageNode = img;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the text to display
|
||||||
|
text.innerHTML = item.Label;
|
||||||
|
|
||||||
|
// Add the div to the parent and recurse into children
|
||||||
|
parent_node.appendChild(node);
|
||||||
|
GenerateTree(self, children, item.Children, depth + 1);
|
||||||
|
item.ChildrenNode = children;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the wrap-around
|
||||||
|
if (depth >= self.HorizontalLayoutDepth)
|
||||||
|
DOM.Node.AppendClearFloat(parent_node.parentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseScroll(self, evt)
|
||||||
|
{
|
||||||
|
// Get mouse wheel movement
|
||||||
|
var delta = evt.detail ? evt.detail * -1 : evt.wheelDelta;
|
||||||
|
delta *= 8;
|
||||||
|
|
||||||
|
// Scroll the main window with wheel movement and clamp
|
||||||
|
self.Node.scrollTop -= delta;
|
||||||
|
self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2);
|
||||||
|
|
||||||
|
self.UpdateScrollbar();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseDoubleClick(self, evt)
|
||||||
|
{
|
||||||
|
DOM.Event.StopDefaultAction(evt);
|
||||||
|
|
||||||
|
// Get the tree view item being clicked, if any
|
||||||
|
var node = DOM.Event.GetNode(evt);
|
||||||
|
var tvitem = GetTreeviewItemFromNode(self, node);
|
||||||
|
if (tvitem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tvitem.Children.length)
|
||||||
|
tvitem.Toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseDown(self, evt)
|
||||||
|
{
|
||||||
|
DOM.Event.StopDefaultAction(evt);
|
||||||
|
|
||||||
|
// Get the tree view item being clicked, if any
|
||||||
|
var node = DOM.Event.GetNode(evt);
|
||||||
|
var tvitem = GetTreeviewItemFromNode(self, node);
|
||||||
|
if (tvitem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If clicking on the image, expand any children
|
||||||
|
if (node.tagName == "IMG" && tvitem.Children.length)
|
||||||
|
{
|
||||||
|
tvitem.Toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var mouse_pos = DOM.Event.GetMousePosition(evt);
|
||||||
|
self.SelectItem(tvitem, mouse_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseUp(evt)
|
||||||
|
{
|
||||||
|
// Event handler used merely to stop events bubbling up to containers
|
||||||
|
DOM.Event.StopPropagation(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseDown_Scrollbar(self, evt)
|
||||||
|
{
|
||||||
|
self.ScrollbarHeld = true;
|
||||||
|
|
||||||
|
// Cache the mouse height relative to the scrollbar
|
||||||
|
self.LastY = evt.clientY;
|
||||||
|
self.ScrollY = self.Node.scrollTop;
|
||||||
|
|
||||||
|
DOM.Node.AddClass(self.ScrollbarNode, "TreeviewScrollbarHeld");
|
||||||
|
DOM.Event.StopDefaultAction(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseUp_Scrollbar(self, evt)
|
||||||
|
{
|
||||||
|
self.ScrollbarHeld = false;
|
||||||
|
DOM.Node.RemoveClass(self.ScrollbarNode, "TreeviewScrollbarHeld");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function OnMouseMove_Scrollbar(self, evt)
|
||||||
|
{
|
||||||
|
if (self.ScrollbarHeld)
|
||||||
|
{
|
||||||
|
var delta_y = evt.clientY - self.LastY;
|
||||||
|
self.LastY = evt.clientY;
|
||||||
|
|
||||||
|
var max_height = self.Node.offsetHeight - Margin;
|
||||||
|
var max_scrollbar_offset = max_height - self.ScrollbarNode.offsetHeight;
|
||||||
|
var max_contents_scroll = self.Node.scrollHeight - self.Node.offsetHeight;
|
||||||
|
var scale = max_contents_scroll / max_scrollbar_offset;
|
||||||
|
|
||||||
|
// Increment the local float variable and assign, as scrollTop is of type int
|
||||||
|
self.ScrollY += delta_y * scale;
|
||||||
|
self.Node.scrollTop = self.ScrollY;
|
||||||
|
self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2);
|
||||||
|
|
||||||
|
self.UpdateScrollbar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetTreeviewItemFromNode(self, node)
|
||||||
|
{
|
||||||
|
// Walk up toward the tree view node looking for this first item
|
||||||
|
while (node && node != self.Node)
|
||||||
|
{
|
||||||
|
if ("TreeviewItem" in node)
|
||||||
|
return node.TreeviewItem;
|
||||||
|
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Treeview;
|
||||||
|
})();
|
||||||
109
vis/extern/BrowserLib/WindowManager/Code/TreeviewItem.js
vendored
Normal file
109
vis/extern/BrowserLib/WindowManager/Code/TreeviewItem.js
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
|
||||||
|
namespace("WM");
|
||||||
|
|
||||||
|
|
||||||
|
WM.TreeviewItem = (function()
|
||||||
|
{
|
||||||
|
function TreeviewItem(treeview, name, data, open_image, close_image)
|
||||||
|
{
|
||||||
|
// Assign members
|
||||||
|
this.Treeview = treeview;
|
||||||
|
this.Label = name;
|
||||||
|
this.Data = data;
|
||||||
|
this.OpenImage = open_image;
|
||||||
|
this.CloseImage = close_image;
|
||||||
|
|
||||||
|
this.Children = [ ];
|
||||||
|
|
||||||
|
// The HTML node wrapping the item and its children
|
||||||
|
this.Node = null;
|
||||||
|
|
||||||
|
// The HTML node storing the image for the open/close state feedback
|
||||||
|
this.ImageNode = null;
|
||||||
|
|
||||||
|
// The HTML node storing just the children
|
||||||
|
this.ChildrenNode = null;
|
||||||
|
|
||||||
|
// Animation handle for opening and closing the child nodes, only used
|
||||||
|
// if the tree view item as children
|
||||||
|
this.AnimHandle = null;
|
||||||
|
|
||||||
|
// Open state of the item
|
||||||
|
this.IsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TreeviewItem.prototype.AddItem = function(name, data, open_image, close_image)
|
||||||
|
{
|
||||||
|
var item = new WM.TreeviewItem(this.Treeview, name, data, open_image, close_image);
|
||||||
|
this.Children.push(item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TreeviewItem.prototype.Open = function()
|
||||||
|
{
|
||||||
|
if (this.AnimHandle == null || this.AnimHandle.Complete)
|
||||||
|
{
|
||||||
|
// Swap to the open state
|
||||||
|
this.IsOpen = true;
|
||||||
|
if (this.ImageNode != null && this.OpenImage != null)
|
||||||
|
this.ImageNode.src = this.OpenImage.src;
|
||||||
|
|
||||||
|
// Cache for closure binding
|
||||||
|
var child_node = this.ChildrenNode;
|
||||||
|
var end_height = this.StartHeight;
|
||||||
|
var treeview = this.Treeview;
|
||||||
|
|
||||||
|
// Reveal the children and animate their height to max
|
||||||
|
this.ChildrenNode.style.display = "block";
|
||||||
|
this.AnimHandle = Anim.Animate(
|
||||||
|
function (val) { DOM.Node.SetHeight(child_node, val) },
|
||||||
|
0, end_height, 0.2,
|
||||||
|
function() { treeview.UpdateScrollbar(); });
|
||||||
|
|
||||||
|
// Fade the children in
|
||||||
|
Anim.Animate(function(val) { DOM.Node.SetOpacity(child_node, val) }, 0, 1, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TreeviewItem.prototype.Close = function()
|
||||||
|
{
|
||||||
|
if (this.AnimHandle == null || this.AnimHandle.Complete)
|
||||||
|
{
|
||||||
|
// Swap to the close state
|
||||||
|
this.IsOpen = false;
|
||||||
|
if (this.ImageNode != null && this.CloseImage != null)
|
||||||
|
this.ImageNode.src = this.CloseImage.src;
|
||||||
|
|
||||||
|
// Cache for closure binding
|
||||||
|
var child_node = this.ChildrenNode;
|
||||||
|
var treeview = this.Treeview;
|
||||||
|
|
||||||
|
// Mark the height of the item for reload later
|
||||||
|
this.StartHeight = child_node.offsetHeight;
|
||||||
|
|
||||||
|
// Shrink the height of the children and hide them upon completion
|
||||||
|
this.AnimHandle = Anim.Animate(
|
||||||
|
function (val) { DOM.Node.SetHeight(child_node, val) },
|
||||||
|
this.ChildrenNode.offsetHeight, 0, 0.2,
|
||||||
|
function() { child_node.style.display = "none"; treeview.UpdateScrollbar(); });
|
||||||
|
|
||||||
|
// Fade the children out
|
||||||
|
Anim.Animate(function(val) { DOM.Node.SetOpacity(child_node, val) }, 1, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TreeviewItem.prototype.Toggle = function()
|
||||||
|
{
|
||||||
|
if (this.IsOpen)
|
||||||
|
this.Close();
|
||||||
|
else
|
||||||
|
this.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return TreeviewItem;
|
||||||
|
})();
|
||||||
314
vis/extern/BrowserLib/WindowManager/Code/Window.js
vendored
Normal file
314
vis/extern/BrowserLib/WindowManager/Code/Window.js
vendored
Normal file
|
|
@ -0,0 +1,314 @@
|
||||||
|
|
||||||
|
namespace("WM");
|
||||||
|
|
||||||
|
|
||||||
|
WM.Window = (function()
|
||||||
|
{
|
||||||
|
var template_html = multiline(function(){/* \
|
||||||
|
<div class='Window'>
|
||||||
|
<div class='WindowTitleBar'>
|
||||||
|
<div class='WindowTitleBarText notextsel' style='float:left'>Window Title Bar</div>
|
||||||
|
<div class='WindowTitleBarClose notextsel' style='float:right'>✕</div>
|
||||||
|
</div>
|
||||||
|
<div class='WindowBody'>
|
||||||
|
</div>
|
||||||
|
<div class='WindowResizeHandle notextsel'>⋰</div>
|
||||||
|
</div>
|
||||||
|
*/});
|
||||||
|
|
||||||
|
|
||||||
|
function Window(manager, title, x, y, width, height, parent_node)
|
||||||
|
{
|
||||||
|
this.Manager = manager;
|
||||||
|
this.ParentNode = parent_node || document.body;
|
||||||
|
this.OnMove = null;
|
||||||
|
this.OnResize = null;
|
||||||
|
this.Visible = false;
|
||||||
|
this.AnimatedShow = false;
|
||||||
|
|
||||||
|
// Clone the window template and locate key nodes within it
|
||||||
|
this.Node = DOM.Node.CreateHTML(template_html);
|
||||||
|
this.TitleBarNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBar");
|
||||||
|
this.TitleBarTextNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBarText");
|
||||||
|
this.TitleBarCloseNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBarClose");
|
||||||
|
this.ResizeHandleNode = DOM.Node.FindWithClass(this.Node, "WindowResizeHandle");
|
||||||
|
this.BodyNode = DOM.Node.FindWithClass(this.Node, "WindowBody");
|
||||||
|
|
||||||
|
// Setup the position and dimensions of the window
|
||||||
|
this.SetPosition(x, y);
|
||||||
|
this.SetSize(width, height);
|
||||||
|
|
||||||
|
// Set the title text
|
||||||
|
this.TitleBarTextNode.innerHTML = title;
|
||||||
|
|
||||||
|
// Hook up event handlers
|
||||||
|
DOM.Event.AddHandler(this.Node, "mousedown", Bind(this, "SetTop"));
|
||||||
|
DOM.Event.AddHandler(this.TitleBarNode, "mousedown", Bind(this, "BeginMove"));
|
||||||
|
DOM.Event.AddHandler(this.ResizeHandleNode, "mousedown", Bind(this, "BeginResize"));
|
||||||
|
DOM.Event.AddHandler(this.TitleBarCloseNode, "mouseup", Bind(this, "Hide"));
|
||||||
|
|
||||||
|
// Create delegates for removable handlers
|
||||||
|
this.MoveDelegate = Bind(this, "Move");
|
||||||
|
this.EndMoveDelegate = Bind(this, "EndMove")
|
||||||
|
this.ResizeDelegate = Bind(this, "Resize");
|
||||||
|
this.EndResizeDelegate = Bind(this, "EndResize");
|
||||||
|
}
|
||||||
|
|
||||||
|
Window.prototype.SetOnMove = function(on_move)
|
||||||
|
{
|
||||||
|
this.OnMove = on_move;
|
||||||
|
}
|
||||||
|
|
||||||
|
Window.prototype.SetOnResize = function(on_resize)
|
||||||
|
{
|
||||||
|
this.OnResize = on_resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.Show = function()
|
||||||
|
{
|
||||||
|
if (this.Node.parentNode != this.ParentNode)
|
||||||
|
{
|
||||||
|
this.ShowNoAnim();
|
||||||
|
Anim.Animate(Bind(this, "OpenAnimation"), 0, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.ShowNoAnim = function()
|
||||||
|
{
|
||||||
|
// Add to the document
|
||||||
|
this.ParentNode.appendChild(this.Node);
|
||||||
|
this.AnimatedShow = false;
|
||||||
|
this.Visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.Hide = function(evt)
|
||||||
|
{
|
||||||
|
if (this.Node.parentNode == this.ParentNode && evt.button == 0)
|
||||||
|
{
|
||||||
|
if (this.AnimatedShow)
|
||||||
|
{
|
||||||
|
// Trigger animation that ends with removing the window from the document
|
||||||
|
Anim.Animate(
|
||||||
|
Bind(this, "CloseAnimation"),
|
||||||
|
0, 1, 0.25,
|
||||||
|
Bind(this, "HideNoAnim"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.HideNoAnim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.HideNoAnim = function()
|
||||||
|
{
|
||||||
|
if (this.Node.parentNode == this.ParentNode)
|
||||||
|
{
|
||||||
|
// Remove node
|
||||||
|
this.ParentNode.removeChild(this.Node);
|
||||||
|
this.Visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.Close = function()
|
||||||
|
{
|
||||||
|
this.HideNoAnim();
|
||||||
|
this.Manager.RemoveWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.SetTop = function()
|
||||||
|
{
|
||||||
|
this.Manager.SetTopWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.SetTitle = function(title)
|
||||||
|
{
|
||||||
|
this.TitleBarTextNode.innerHTML = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Update this
|
||||||
|
Window.prototype.AddControl = function(control)
|
||||||
|
{
|
||||||
|
// Get all arguments to this function and replace the first with this window node
|
||||||
|
var args = [].slice.call(arguments);
|
||||||
|
args[0] = this.BodyNode;
|
||||||
|
|
||||||
|
// Create the control and call its Init method with the modified arguments
|
||||||
|
var instance = new control();
|
||||||
|
instance.Init.apply(instance, args);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.AddControlNew = function(control)
|
||||||
|
{
|
||||||
|
control.ParentNode = this.BodyNode;
|
||||||
|
this.BodyNode.appendChild(control.Node);
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.RemoveControl = function(control)
|
||||||
|
{
|
||||||
|
if (control.ParentNode == this.BodyNode)
|
||||||
|
{
|
||||||
|
control.ParentNode.removeChild(control.Node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.Scale = function(t)
|
||||||
|
{
|
||||||
|
// Calculate window bounds centre/extents
|
||||||
|
var ext_x = this.Size[0] / 2;
|
||||||
|
var ext_y = this.Size[1] / 2;
|
||||||
|
var mid_x = this.Position[0] + ext_x;
|
||||||
|
var mid_y = this.Position[1] + ext_y;
|
||||||
|
|
||||||
|
// Scale from the mid-point
|
||||||
|
DOM.Node.SetPosition(this.Node, [ mid_x - ext_x * t, mid_y - ext_y * t ]);
|
||||||
|
DOM.Node.SetSize(this.Node, [ this.Size[0] * t, this.Size[1] * t ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.OpenAnimation = function(val)
|
||||||
|
{
|
||||||
|
// Power ease in
|
||||||
|
var t = 1 - Math.pow(1 - val, 8);
|
||||||
|
this.Scale(t);
|
||||||
|
DOM.Node.SetOpacity(this.Node, 1 - Math.pow(1 - val, 8));
|
||||||
|
this.AnimatedShow = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.CloseAnimation = function(val)
|
||||||
|
{
|
||||||
|
// Power ease out
|
||||||
|
var t = 1 - Math.pow(val, 4);
|
||||||
|
this.Scale(t);
|
||||||
|
DOM.Node.SetOpacity(this.Node, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.NotifyChange = function()
|
||||||
|
{
|
||||||
|
if (this.OnMove)
|
||||||
|
{
|
||||||
|
var pos = DOM.Node.GetPosition(this.Node);
|
||||||
|
this.OnMove(this, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.BeginMove = function(evt)
|
||||||
|
{
|
||||||
|
// Calculate offset of the window from the mouse down position
|
||||||
|
var mouse_pos = DOM.Event.GetMousePosition(evt);
|
||||||
|
this.Offset = [ mouse_pos[0] - this.Position[0], mouse_pos[1] - this.Position[1] ];
|
||||||
|
|
||||||
|
// Dynamically add handlers for movement and release
|
||||||
|
DOM.Event.AddHandler(document, "mousemove", this.MoveDelegate);
|
||||||
|
DOM.Event.AddHandler(document, "mouseup", this.EndMoveDelegate);
|
||||||
|
|
||||||
|
DOM.Event.StopDefaultAction(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.Move = function(evt)
|
||||||
|
{
|
||||||
|
// Use the offset at the beginning of movement to drag the window around
|
||||||
|
var mouse_pos = DOM.Event.GetMousePosition(evt);
|
||||||
|
var offset = this.Offset;
|
||||||
|
var pos = [ mouse_pos[0] - offset[0], mouse_pos[1] - offset[1] ];
|
||||||
|
this.SetPosition(pos[0], pos[1]);
|
||||||
|
|
||||||
|
if (this.OnMove)
|
||||||
|
this.OnMove(this, pos);
|
||||||
|
|
||||||
|
DOM.Event.StopDefaultAction(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.EndMove = function(evt)
|
||||||
|
{
|
||||||
|
// Remove handlers added during mouse down
|
||||||
|
DOM.Event.RemoveHandler(document, "mousemove", this.MoveDelegate);
|
||||||
|
DOM.Event.RemoveHandler(document, "mouseup", this.EndMoveDelegate);
|
||||||
|
|
||||||
|
DOM.Event.StopDefaultAction(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.BeginResize = function(evt)
|
||||||
|
{
|
||||||
|
// Calculate offset of the window from the mouse down position
|
||||||
|
var mouse_pos = DOM.Event.GetMousePosition(evt);
|
||||||
|
this.MousePosBeforeResize = [ mouse_pos[0], mouse_pos[1] ];
|
||||||
|
this.SizeBeforeResize = this.Size;
|
||||||
|
|
||||||
|
// Dynamically add handlers for movement and release
|
||||||
|
DOM.Event.AddHandler(document, "mousemove", this.ResizeDelegate);
|
||||||
|
DOM.Event.AddHandler(document, "mouseup", this.EndResizeDelegate);
|
||||||
|
|
||||||
|
DOM.Event.StopDefaultAction(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.Resize = function(evt)
|
||||||
|
{
|
||||||
|
// Use the offset at the beginning of movement to drag the window around
|
||||||
|
var mouse_pos = DOM.Event.GetMousePosition(evt);
|
||||||
|
var offset = [ mouse_pos[0] - this.MousePosBeforeResize[0], mouse_pos[1] - this.MousePosBeforeResize[1] ];
|
||||||
|
this.SetSize(this.SizeBeforeResize[0] + offset[0], this.SizeBeforeResize[1] + offset[1]);
|
||||||
|
|
||||||
|
if (this.OnResize)
|
||||||
|
this.OnResize(this, this.Size);
|
||||||
|
|
||||||
|
DOM.Event.StopDefaultAction(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.EndResize = function(evt)
|
||||||
|
{
|
||||||
|
// Remove handlers added during mouse down
|
||||||
|
DOM.Event.RemoveHandler(document, "mousemove", this.ResizeDelegate);
|
||||||
|
DOM.Event.RemoveHandler(document, "mouseup", this.EndResizeDelegate);
|
||||||
|
|
||||||
|
DOM.Event.StopDefaultAction(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.SetPosition = function(x, y)
|
||||||
|
{
|
||||||
|
this.Position = [ x, y ];
|
||||||
|
DOM.Node.SetPosition(this.Node, this.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.SetSize = function(w, h)
|
||||||
|
{
|
||||||
|
w = Math.max(80, w);
|
||||||
|
h = Math.max(15, h);
|
||||||
|
this.Size = [ w, h ];
|
||||||
|
DOM.Node.SetSize(this.Node, this.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Window.prototype.GetZIndex = function()
|
||||||
|
{
|
||||||
|
return parseInt(this.Node.style.zIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return Window;
|
||||||
|
})();
|
||||||
65
vis/extern/BrowserLib/WindowManager/Code/WindowManager.js
vendored
Normal file
65
vis/extern/BrowserLib/WindowManager/Code/WindowManager.js
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
|
||||||
|
namespace("WM");
|
||||||
|
|
||||||
|
|
||||||
|
WM.WindowManager = (function()
|
||||||
|
{
|
||||||
|
function WindowManager()
|
||||||
|
{
|
||||||
|
// An empty list of windows under window manager control
|
||||||
|
this.Windows = [ ];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WindowManager.prototype.AddWindow = function(title, x, y, width, height, parent_node)
|
||||||
|
{
|
||||||
|
// Create the window and add it to the list of windows
|
||||||
|
var wnd = new WM.Window(this, title, x, y, width, height, parent_node);
|
||||||
|
this.Windows.push(wnd);
|
||||||
|
|
||||||
|
// Always bring to the top on creation
|
||||||
|
wnd.SetTop();
|
||||||
|
|
||||||
|
return wnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WindowManager.prototype.RemoveWindow = function(window)
|
||||||
|
{
|
||||||
|
// Remove from managed window list
|
||||||
|
var index = this.Windows.indexOf(window);
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
this.Windows.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WindowManager.prototype.SetTopWindow = function(top_wnd)
|
||||||
|
{
|
||||||
|
// Bring the window to the top of the window list
|
||||||
|
var top_wnd_index = this.Windows.indexOf(top_wnd);
|
||||||
|
if (top_wnd_index != -1)
|
||||||
|
this.Windows.splice(top_wnd_index, 1);
|
||||||
|
this.Windows.push(top_wnd);
|
||||||
|
|
||||||
|
// Set a CSS z-index for each visible window from the bottom up
|
||||||
|
for (var i in this.Windows)
|
||||||
|
{
|
||||||
|
var wnd = this.Windows[i];
|
||||||
|
if (!wnd.Visible)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Ensure there's space between each window for the elements inside to be sorted
|
||||||
|
var z = (parseInt(i) + 1) * 10;
|
||||||
|
wnd.Node.style.zIndex = z;
|
||||||
|
|
||||||
|
// Notify window that its z-order has changed
|
||||||
|
wnd.NotifyChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return WindowManager;
|
||||||
|
|
||||||
|
})();
|
||||||
652
vis/extern/BrowserLib/WindowManager/Styles/WindowManager.css
vendored
Normal file
652
vis/extern/BrowserLib/WindowManager/Styles/WindowManager.css
vendored
Normal file
|
|
@ -0,0 +1,652 @@
|
||||||
|
|
||||||
|
|
||||||
|
.notextsel
|
||||||
|
{
|
||||||
|
/* Disable text selection so that it doesn't interfere with button-clicking */
|
||||||
|
user-select: none;
|
||||||
|
-moz-user-select: none; /* Firefox */
|
||||||
|
-ms-user-select: none; /* Internet Explorer */
|
||||||
|
-khtml-user-select: none; /* KHTML browsers (e.g. Konqueror) */
|
||||||
|
-webkit-user-select: none; /* Chrome, Safari, and Opera */
|
||||||
|
-webkit-touch-callout: none; /* Disable Android and iOS callouts*/
|
||||||
|
|
||||||
|
/* Stops the text cursor over the label */
|
||||||
|
cursor:default;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* Window Styles */
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
body
|
||||||
|
{
|
||||||
|
/* Clip contents to browser window without adding scrollbars */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Window
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
|
||||||
|
/* Clip all contents to the window border */
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
background: #555;
|
||||||
|
|
||||||
|
/*padding: 0px !important;*/
|
||||||
|
|
||||||
|
border-radius: 3px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset;
|
||||||
|
box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*:root
|
||||||
|
{
|
||||||
|
--SideBarSize: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WindowBodyDebug
|
||||||
|
{
|
||||||
|
color: #BBB;
|
||||||
|
font: 9px Verdana;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WindowSizeLeft
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
width: var(--SideBarSize);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.WindowSizeRight
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
left: calc(100% - var(--SideBarSize));
|
||||||
|
top:0px;
|
||||||
|
width: var(--SideBarSize);
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
.WindowSizeTop
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: var(--SideBarSize);
|
||||||
|
}
|
||||||
|
.WindowSizeBottom
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
top: calc(100% - var(--SideBarSize));
|
||||||
|
width: 100%;
|
||||||
|
height: var(--SideBarSize);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
.Window_Transparent
|
||||||
|
{
|
||||||
|
/* Set transparency changes to fade in/out */
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity 0.5s ease-out;
|
||||||
|
-moz-transition: opacity 0.5s ease-out;
|
||||||
|
-webkit-transition: opacity 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Window_Transparent:hover
|
||||||
|
{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WindowTitleBar
|
||||||
|
{
|
||||||
|
height: 17px;
|
||||||
|
cursor: move;
|
||||||
|
/*overflow: hidden;*/
|
||||||
|
|
||||||
|
border-bottom: 1px solid #303030;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WindowTitleBarText
|
||||||
|
{
|
||||||
|
color: #BBB;
|
||||||
|
font: 9px Verdana;
|
||||||
|
/*white-space: nowrap;*/
|
||||||
|
|
||||||
|
padding: 3px;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WindowTitleBarClose
|
||||||
|
{
|
||||||
|
color: #999999;
|
||||||
|
font: 9px Verdana;
|
||||||
|
|
||||||
|
padding: 3px;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WindowTitleBarClose:hover {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WindowResizeHandle
|
||||||
|
{
|
||||||
|
color: #999999;
|
||||||
|
font: 17px Verdana;
|
||||||
|
padding: 3px;
|
||||||
|
cursor: se-resize;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -7px;
|
||||||
|
right: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WindowBody {
|
||||||
|
position: absolute;
|
||||||
|
/* overflow: hidden; */
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
border-top: 1px solid #606060;
|
||||||
|
top: 18px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* Container Styles */
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.Container
|
||||||
|
{
|
||||||
|
/* Position relative to the parent window */
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
/* Clip contents */
|
||||||
|
/*overflow: hidden;*/
|
||||||
|
|
||||||
|
background:#2C2C2C;
|
||||||
|
|
||||||
|
border: 1px black solid;
|
||||||
|
|
||||||
|
/* Two inset box shadows to simulate depressing */
|
||||||
|
-webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;
|
||||||
|
box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.Panel
|
||||||
|
{*/
|
||||||
|
/* Position relative to the parent window */
|
||||||
|
/*position: absolute;*/
|
||||||
|
|
||||||
|
/* Clip contents */
|
||||||
|
/*overflow: hidden;
|
||||||
|
|
||||||
|
background:#2C2C2C;
|
||||||
|
|
||||||
|
border: 1px black solid;*/
|
||||||
|
|
||||||
|
/* Two inset box shadows to simulate depressing */
|
||||||
|
/*-webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;
|
||||||
|
box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* Ruler Styles */
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*.Ruler
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
border: dashed 1px;
|
||||||
|
|
||||||
|
opacity: 0.35;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* Treeview Styles */
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.Treeview
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
background:#2C2C2C;
|
||||||
|
border: 1px solid black;
|
||||||
|
overflow:hidden;
|
||||||
|
|
||||||
|
/* Two inset box shadows to simulate depressing */
|
||||||
|
-webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;
|
||||||
|
box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TreeviewItem
|
||||||
|
{
|
||||||
|
margin:1px;
|
||||||
|
padding:2px;
|
||||||
|
border:solid 1px #2C2C2C;
|
||||||
|
background-color:#2C2C2C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TreeviewItemImage
|
||||||
|
{
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TreeviewItemText
|
||||||
|
{
|
||||||
|
float: left;
|
||||||
|
margin-left:4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TreeviewItemChildren
|
||||||
|
{
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TreeviewItemSelected
|
||||||
|
{
|
||||||
|
background-color:#444;
|
||||||
|
border-color:#FFF;
|
||||||
|
|
||||||
|
-webkit-transition: background-color 0.2s ease-in-out;
|
||||||
|
-moz-transition: background-color 0.2s ease-in-out;
|
||||||
|
-webkit-transition: border-color 0.2s ease-in-out;
|
||||||
|
-moz-transition: border-color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used to populate treeviews that want highlight on hover behaviour */
|
||||||
|
.TreeviewItemHover
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
.TreeviewItemHover:hover
|
||||||
|
{
|
||||||
|
background-color:#111;
|
||||||
|
border-color:#444;
|
||||||
|
|
||||||
|
-webkit-transition: background-color 0.2s ease-in-out;
|
||||||
|
-moz-transition: background-color 0.2s ease-in-out;
|
||||||
|
-webkit-transition: border-color 0.2s ease-in-out;
|
||||||
|
-moz-transition: border-color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TreeviewScrollbarInset
|
||||||
|
{
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
position:relative;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
/* CRAZINESS PART A: Trying to get the inset and scrollbar to have 100% height match its container */
|
||||||
|
margin: -8px -8px 0 0;
|
||||||
|
padding: 0 1px 14px 1px;
|
||||||
|
|
||||||
|
width:20px;
|
||||||
|
background:#2C2C2C;
|
||||||
|
border: 1px solid black;
|
||||||
|
|
||||||
|
/* Two inset box shadows to simulate depressing */
|
||||||
|
-webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;
|
||||||
|
box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TreeviewScrollbar
|
||||||
|
{
|
||||||
|
position:relative;
|
||||||
|
|
||||||
|
background:#2C2C2C;
|
||||||
|
border: 1px solid black;
|
||||||
|
|
||||||
|
/* CRAZINESS PART B: Trying to get the inset and scrollbar to have 100% height match its container */
|
||||||
|
padding: 0 0 10px 0;
|
||||||
|
margin: 1px 0 0 0;
|
||||||
|
|
||||||
|
width: 18px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
border-radius:6px;
|
||||||
|
border-color:#000;
|
||||||
|
border-width:1px;
|
||||||
|
border-style:solid;
|
||||||
|
|
||||||
|
/* The gradient for the button background */
|
||||||
|
background-color:#666;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, from(#666), to(#383838));
|
||||||
|
background: -moz-linear-gradient(top, #666, #383838);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#666666', endColorstr='#383838');
|
||||||
|
|
||||||
|
/* A box shadow and inset box highlight */
|
||||||
|
-webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset;
|
||||||
|
box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TreeviewScrollbarHeld
|
||||||
|
{
|
||||||
|
/* Reset the gradient to a full-colour background */
|
||||||
|
background:#383838;
|
||||||
|
|
||||||
|
/* Two inset box shadows to simulate depressing */
|
||||||
|
-webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;
|
||||||
|
box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* Edit Box Styles */
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.EditBoxContainer
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
padding:2px 10px 2px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.EditBoxLabel
|
||||||
|
{
|
||||||
|
float:left;
|
||||||
|
padding: 3px 4px 4px 4px;
|
||||||
|
font: 9px Verdana;
|
||||||
|
}
|
||||||
|
|
||||||
|
.EditBox
|
||||||
|
{
|
||||||
|
float:left;
|
||||||
|
|
||||||
|
background:#666;
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 3px 4px 3px 4px;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
box-shadow: 1px 1px 1px #222 inset;
|
||||||
|
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.EditBox:focus
|
||||||
|
{
|
||||||
|
background:#FFF;
|
||||||
|
outline:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* Label Styles */
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.Label
|
||||||
|
{
|
||||||
|
/* Position relative to the parent window */
|
||||||
|
position:absolute;
|
||||||
|
|
||||||
|
color: #BBB;
|
||||||
|
font: 9px Verdana;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* Combo Box Styles */
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.ComboBox
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
|
||||||
|
/* TEMP! */
|
||||||
|
width:90px;
|
||||||
|
|
||||||
|
/* Height is fixed to match the font */
|
||||||
|
height:14px;
|
||||||
|
|
||||||
|
/* Align the text within the combo box */
|
||||||
|
padding: 1px 0 0 5px;
|
||||||
|
|
||||||
|
/* Solid, rounded border */
|
||||||
|
border: 1px solid #111;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
/* http://www.colorzilla.com/gradient-editor/#e3e3e3+0,c6c6c6+22,b7b7b7+33,afafaf+50,a7a7a7+67,797979+82,414141+100;Custom */
|
||||||
|
background: #e3e3e3;
|
||||||
|
background: -moz-linear-gradient(top, #e3e3e3 0%, #c6c6c6 22%, #b7b7b7 33%, #afafaf 50%, #a7a7a7 67%, #797979 82%, #414141 100%);
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#e3e3e3), color-stop(22%,#c6c6c6), color-stop(33%,#b7b7b7), color-stop(50%,#afafaf), color-stop(67%,#a7a7a7), color-stop(82%,#797979), color-stop(100%,#414141));
|
||||||
|
background: -webkit-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%);
|
||||||
|
background: -o-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%);
|
||||||
|
background: -ms-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%);
|
||||||
|
background: linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e3e3e3', endColorstr='#414141',GradientType=0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
.ComboBoxPressed
|
||||||
|
{
|
||||||
|
/* The reverse of the default background, simulating depression */
|
||||||
|
background: #414141;
|
||||||
|
background: -moz-linear-gradient(top, #414141 0%, #797979 18%, #a7a7a7 33%, #afafaf 50%, #b7b7b7 67%, #c6c6c6 78%, #e3e3e3 100%);
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#414141), color-stop(18%,#797979), color-stop(33%,#a7a7a7), color-stop(50%,#afafaf), color-stop(67%,#b7b7b7), color-stop(78%,#c6c6c6), color-stop(100%,#e3e3e3));
|
||||||
|
background: -webkit-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%);
|
||||||
|
background: -o-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%);
|
||||||
|
background: -ms-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%);
|
||||||
|
background: linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#414141', endColorstr='#e3e3e3',GradientType=0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
.ComboBoxText
|
||||||
|
{
|
||||||
|
/* Text info */
|
||||||
|
color: #000;
|
||||||
|
font: 9px Verdana;
|
||||||
|
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ComboBoxIcon
|
||||||
|
{
|
||||||
|
/* Push the image to the far right */
|
||||||
|
float:right;
|
||||||
|
|
||||||
|
/* Align the image with the combo box */
|
||||||
|
padding: 2px 5px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ComboBoxPopup
|
||||||
|
{
|
||||||
|
position: fixed;
|
||||||
|
|
||||||
|
background: #CCC;
|
||||||
|
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
padding: 1px 0 1px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ComboBoxPopupItem
|
||||||
|
{
|
||||||
|
/* Text info */
|
||||||
|
color: #000;
|
||||||
|
font: 9px Verdana;
|
||||||
|
|
||||||
|
padding: 1px 1px 1px 5px;
|
||||||
|
|
||||||
|
border-bottom: 1px solid #AAA;
|
||||||
|
border-top: 1px solid #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ComboBoxPopupItemText
|
||||||
|
{
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ComboBoxPopupItemIcon
|
||||||
|
{
|
||||||
|
/* Push the image to the far right */
|
||||||
|
float:right;
|
||||||
|
|
||||||
|
/* Align the image with the combo box */
|
||||||
|
padding: 2px 5px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ComboBoxPopupItem:first-child
|
||||||
|
{
|
||||||
|
border-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ComboBoxPopupItem:last-child
|
||||||
|
{
|
||||||
|
border-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ComboBoxPopupItem:hover
|
||||||
|
{
|
||||||
|
color:#FFF;
|
||||||
|
background: #2036E1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* Grid Styles */
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
.Grid {
|
||||||
|
overflow: auto;
|
||||||
|
background: #333;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GridBody
|
||||||
|
{
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GridRow
|
||||||
|
{
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
background:#303030;
|
||||||
|
|
||||||
|
color: #BBB;
|
||||||
|
font: 9px Verdana;
|
||||||
|
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GridRow.GridGroup
|
||||||
|
{
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GridRow:nth-child(odd)
|
||||||
|
{
|
||||||
|
background:#333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GridRowCell
|
||||||
|
{
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.GridRowCell.GridGroup
|
||||||
|
{
|
||||||
|
color: #BBB;
|
||||||
|
|
||||||
|
/* Override default from name */
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GridRowBody
|
||||||
|
{
|
||||||
|
/* Clip all contents for show/hide group*/
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
/* Crazy CSS rules: controls for properties don't clip if this isn't set on this parent */
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
/* Button Styles */
|
||||||
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.Button
|
||||||
|
{
|
||||||
|
/* Position relative to the parent window */
|
||||||
|
position:absolute;
|
||||||
|
|
||||||
|
border-radius:4px;
|
||||||
|
|
||||||
|
/* Padding at the top includes 2px for the text drop-shadow */
|
||||||
|
padding: 2px 5px 3px 5px;
|
||||||
|
|
||||||
|
color: #BBB;
|
||||||
|
font: 9px Verdana;
|
||||||
|
text-shadow: 1px 1px 1px black;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
background-color:#555;
|
||||||
|
|
||||||
|
/* A box shadow and inset box highlight */
|
||||||
|
-webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset;
|
||||||
|
box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button:hover {
|
||||||
|
background-color: #616161;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button.ButtonHeld
|
||||||
|
{
|
||||||
|
/* Reset the gradient to a full-colour background */
|
||||||
|
background:#383838;
|
||||||
|
|
||||||
|
/* Two inset box shadows to simulate depressing */
|
||||||
|
-webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;
|
||||||
|
box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;
|
||||||
|
}
|
||||||
61
vis/index.html
Normal file
61
vis/index.html
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<title>Remotery Viewer</title>
|
||||||
|
|
||||||
|
<!-- Style Sheets -->
|
||||||
|
<link rel="stylesheet" type="text/css" href="extern/BrowserLib/WindowManager/Styles/WindowManager.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="Styles/Remotery.css" />
|
||||||
|
|
||||||
|
<!-- Utilities -->
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/Core/Code/Core.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/Core/Code/DOM.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/Core/Code/Bind.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/Core/Code/Animation.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/Core/Code/Convert.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/Core/Code/LocalStore.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/Core/Code/Mouse.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/Core/Code/Keyboard.js"></script>
|
||||||
|
|
||||||
|
<!-- User Interface Window Manager -->
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/WindowManager.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/Window.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/Container.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/EditBox.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/Grid.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/Label.js"></script>
|
||||||
|
<script type="text/javascript" src="extern/BrowserLib/WindowManager/Code/Button.js"></script>
|
||||||
|
|
||||||
|
<!-- Main Application -->
|
||||||
|
<script type="text/javascript" src="Code/WebGL.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/WebGLFont.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/Shaders.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/DataViewReader.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/Console.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/NameMap.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/WebSocketConnection.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/TitleWindow.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/SampleWindow.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/PixelTimeRange.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/TimelineRow.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/TimelineMarkers.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/TimelineWindow.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/ThreadFrame.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/TraceDrop.js"></script>
|
||||||
|
<script type="text/javascript" src="Code/Remotery.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
var remotery = new Remotery();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue