Tracy, vertex ordering
This commit is contained in:
parent
bce746ca01
commit
05c69190d2
|
@ -0,0 +1,3 @@
|
|||
[submodule "include/tracy"]
|
||||
path = include/tracy
|
||||
url = https://github.com/wolfpld/tracy.git
|
|
@ -68,7 +68,7 @@
|
|||
{
|
||||
"label": "exec callgrind",
|
||||
"type": "shell",
|
||||
"command": "valgrind --tool=callgrind --dump-instr=yes --simulate-cache=yes --fair-sched=yes ./univerxel",
|
||||
"command": "valgrind --tool=callgrind --simulate-cache=yes --fair-sched=yes ./univerxel",
|
||||
"options": {
|
||||
"cwd": "${workspaceRoot}/build"
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||
endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
|
||||
# set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=clang-analyzer-*,cppcoreguidelines-*,performance-*,readability-*,-readability-braces-around-statements,-readability-uppercase-literal-suffix")
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
|
@ -39,21 +39,26 @@ add_definitions(
|
|||
)
|
||||
|
||||
file(GLOB_RECURSE SOURCES "src/*/*.cpp")
|
||||
file(GLOB INCLUDE_SOURCES "include/imgui-1.76/*.cpp" "include/FastNoiseSIMD/*.cpp" "include/Remotery/lib/*.c")
|
||||
file(GLOB INCLUDE_SOURCES "include/imgui-1.76/*.cpp" "include/FastNoiseSIMD/*.cpp" "include/tracy/TracyClient.cpp")
|
||||
set(INCLUDE_LIBS
|
||||
"include/imgui-1.76"
|
||||
"include/FastNoiseSIMD"
|
||||
"include/toml++"
|
||||
"include/Remotery/lib"
|
||||
"include/robin_hood"
|
||||
"include/libguarded"
|
||||
"include/tracy"
|
||||
)
|
||||
|
||||
add_executable(univerxel "src/main.cpp" ${SOURCES} ${INCLUDE_SOURCES})
|
||||
target_compile_features(univerxel PUBLIC cxx_std_17)
|
||||
target_link_libraries(univerxel ${LINKED_LIBS})
|
||||
target_include_directories(univerxel PRIVATE ${INCLUDE_LIBS})
|
||||
target_compile_definitions(univerxel PRIVATE RMT_ENABLED=${PROFILING} RMT_USE_OPENGL=${PROFILING} FIXED_WINDOW=${FIXED_WINDOW} HN_USE_FILESYSTEM=1)
|
||||
if(PROFILING)
|
||||
target_compile_definitions(univerxel PRIVATE TRACY_ENABLE=1 FIXED_WINDOW=${FIXED_WINDOW} HN_USE_FILESYSTEM=1)
|
||||
else(PROFILING)
|
||||
target_compile_definitions(univerxel PRIVATE FIXED_WINDOW=${FIXED_WINDOW} HN_USE_FILESYSTEM=1)
|
||||
endif(PROFILING)
|
||||
|
||||
|
||||
file(COPY content/shaders DESTINATION ${CMAKE_BINARY_DIR}/content)
|
||||
file(COPY content/textures DESTINATION ${CMAKE_BINARY_DIR}/content)
|
||||
|
|
|
@ -29,7 +29,7 @@ Experimental project using OpenGL.
|
|||
* ImGui
|
||||
* FasNoiseSIMD
|
||||
* Toml++
|
||||
* Remotery
|
||||
* Tracy
|
||||
* Love and insomnia
|
||||
|
||||
|
||||
|
@ -56,7 +56,7 @@ To get a local copy up and running, follow these simple steps.
|
|||
|
||||
1. Clone the project repo
|
||||
```sh
|
||||
git clone https://git.wadza.fr/me/univerxel.git
|
||||
git clone --recursive https://git.wadza.fr/me/univerxel.git
|
||||
```
|
||||
2. Create build folder and move
|
||||
```sh
|
||||
|
|
6
TODO.md
6
TODO.md
|
@ -4,8 +4,9 @@
|
|||
- [x] Generate noise
|
||||
- [x] Density
|
||||
- [x] Robin hood map
|
||||
- [ ] In memory RLE
|
||||
- [ ] Octree world
|
||||
- [ ] Memory usage
|
||||
- [ ] In memory RLE
|
||||
- [ ] Octree world
|
||||
- [x] Serialize
|
||||
- [x] Group files
|
||||
- [x] Zstd + custom grouping
|
||||
|
@ -66,6 +67,7 @@
|
|||
- [ ] Deferred
|
||||
- [ ] Cascaded shadow maps
|
||||
- [ ] RayCast
|
||||
- [~] Transparency
|
||||
- [x] Float precision problem
|
||||
|
||||
## Contouring
|
||||
|
|
|
@ -1,175 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
File diff suppressed because it is too large
Load Diff
|
@ -1,659 +0,0 @@
|
|||
|
||||
|
||||
/*
|
||||
Copyright 2014-2018 Celtoys Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Compiling
|
||||
---------
|
||||
|
||||
* Windows (MSVC) - add lib/Remotery.c and lib/Remotery.h to your program. Set include
|
||||
directories to add Remotery/lib path. The required library ws2_32.lib should be picked
|
||||
up through the use of the #pragma comment(lib, "ws2_32.lib") directive in Remotery.c.
|
||||
|
||||
* Mac OS X (XCode) - simply add lib/Remotery.c and lib/Remotery.h to your program.
|
||||
|
||||
* Linux (GCC) - add the source in lib folder. Compilation of the code requires -pthreads for
|
||||
library linkage. For example to compile the same run: cc lib/Remotery.c sample/sample.c
|
||||
-I lib -pthread -lm
|
||||
|
||||
You can define some extra macros to modify what features are compiled into Remotery. These are
|
||||
documented just below this comment.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef RMT_INCLUDED_H
|
||||
#define RMT_INCLUDED_H
|
||||
|
||||
|
||||
// Set to 0 to not include any bits of Remotery in your build
|
||||
#ifndef RMT_ENABLED
|
||||
#define RMT_ENABLED 1
|
||||
#endif
|
||||
|
||||
// Help performance of the server sending data to the client by marking this machine as little-endian
|
||||
#ifndef RMT_ASSUME_LITTLE_ENDIAN
|
||||
#define RMT_ASSUME_LITTLE_ENDIAN 0
|
||||
#endif
|
||||
|
||||
// Used by the Celtoys TinyCRT library (not released yet)
|
||||
#ifndef RMT_USE_TINYCRT
|
||||
#define RMT_USE_TINYCRT 0
|
||||
#endif
|
||||
|
||||
// Assuming CUDA headers/libs are setup, allow CUDA profiling
|
||||
#ifndef RMT_USE_CUDA
|
||||
#define RMT_USE_CUDA 0
|
||||
#endif
|
||||
|
||||
// Assuming Direct3D 11 headers/libs are setup, allow D3D11 profiling
|
||||
#ifndef RMT_USE_D3D11
|
||||
#define RMT_USE_D3D11 0
|
||||
#endif
|
||||
|
||||
// Allow OpenGL profiling
|
||||
#ifndef RMT_USE_OPENGL
|
||||
#define RMT_USE_OPENGL 0
|
||||
#endif
|
||||
|
||||
// Allow Metal profiling
|
||||
#ifndef RMT_USE_METAL
|
||||
#define RMT_USE_METAL 0
|
||||
#endif
|
||||
|
||||
// Initially use POSIX thread names to name threads instead of Thread0, 1, ...
|
||||
#ifndef RMT_USE_POSIX_THREADNAMES
|
||||
#define RMT_USE_POSIX_THREADNAMES 0
|
||||
#endif
|
||||
|
||||
// How many times we spin data back and forth between CPU & GPU
|
||||
// to calculate average RTT (Roundtrip Time). Cannot be 0.
|
||||
// Affects OpenGL & D3D11
|
||||
#ifndef RMT_GPU_CPU_SYNC_NUM_ITERATIONS
|
||||
#define RMT_GPU_CPU_SYNC_NUM_ITERATIONS 16
|
||||
#endif
|
||||
|
||||
// Time in seconds between each resync to compensate for drifting between GPU & CPU timers,
|
||||
// effects of power saving, etc. Resyncs can cause stutter, lag spikes, stalls.
|
||||
// Set to 0 for never.
|
||||
// Affects OpenGL & D3D11
|
||||
#ifndef RMT_GPU_CPU_SYNC_SECONDS
|
||||
#define RMT_GPU_CPU_SYNC_SECONDS 30
|
||||
#endif
|
||||
|
||||
// Whether we should automatically resync if we detect a timer disjoint (e.g.
|
||||
// changed from AC power to battery, GPU is overheating, or throttling up/down
|
||||
// due to laptop savings events). Set it to 0 to avoid resync in such events.
|
||||
// Useful if for some odd reason a driver reports a lot of disjoints.
|
||||
// Affects D3D11
|
||||
#ifndef RMT_D3D11_RESYNC_ON_DISJOINT
|
||||
#define RMT_D3D11_RESYNC_ON_DISJOINT 1
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
Compiler/Platform Detection and Preprocessor Utilities
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
// Platform identification
|
||||
#if defined(_WINDOWS) || defined(_WIN32)
|
||||
#define RMT_PLATFORM_WINDOWS
|
||||
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
#define RMT_PLATFORM_LINUX
|
||||
#define RMT_PLATFORM_POSIX
|
||||
#elif defined(__APPLE__)
|
||||
#define RMT_PLATFORM_MACOS
|
||||
#define RMT_PLATFORM_POSIX
|
||||
#endif
|
||||
|
||||
#ifdef RMT_DLL
|
||||
#if defined (RMT_PLATFORM_WINDOWS)
|
||||
#if defined (RMT_IMPL)
|
||||
#define RMT_API __declspec(dllexport)
|
||||
#else
|
||||
#define RMT_API __declspec(dllimport)
|
||||
#endif
|
||||
#elif defined (RMT_PLATFORM_POSIX)
|
||||
#if defined (RMT_IMPL)
|
||||
#define RMT_API __attribute__((visibility("default")))
|
||||
#else
|
||||
#define RMT_API
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#define RMT_API
|
||||
#endif
|
||||
|
||||
// Allows macros to be written that can work around the inability to do: #define(x) #ifdef x
|
||||
// with the C preprocessor.
|
||||
#if RMT_ENABLED
|
||||
#define IFDEF_RMT_ENABLED(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_ENABLED(t, f) f
|
||||
#endif
|
||||
#if RMT_ENABLED && RMT_USE_CUDA
|
||||
#define IFDEF_RMT_USE_CUDA(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_USE_CUDA(t, f) f
|
||||
#endif
|
||||
#if RMT_ENABLED && RMT_USE_D3D11
|
||||
#define IFDEF_RMT_USE_D3D11(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_USE_D3D11(t, f) f
|
||||
#endif
|
||||
#if RMT_ENABLED && RMT_USE_OPENGL
|
||||
#define IFDEF_RMT_USE_OPENGL(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_USE_OPENGL(t, f) f
|
||||
#endif
|
||||
#if RMT_ENABLED && RMT_USE_METAL
|
||||
#define IFDEF_RMT_USE_METAL(t, f) t
|
||||
#else
|
||||
#define IFDEF_RMT_USE_METAL(t, f) f
|
||||
#endif
|
||||
|
||||
|
||||
// Public interface is written in terms of these macros to easily enable/disable itself
|
||||
#define RMT_OPTIONAL(macro, x) IFDEF_ ## macro(x, )
|
||||
#define RMT_OPTIONAL_RET(macro, x, y) IFDEF_ ## macro(x, (y))
|
||||
|
||||
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
Types
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// Boolean
|
||||
typedef unsigned int rmtBool;
|
||||
#define RMT_TRUE ((rmtBool)1)
|
||||
#define RMT_FALSE ((rmtBool)0)
|
||||
|
||||
|
||||
// Unsigned integer types
|
||||
typedef unsigned char rmtU8;
|
||||
typedef unsigned short rmtU16;
|
||||
typedef unsigned int rmtU32;
|
||||
typedef unsigned long long rmtU64;
|
||||
|
||||
|
||||
// Signed integer types
|
||||
typedef char rmtS8;
|
||||
typedef short rmtS16;
|
||||
typedef int rmtS32;
|
||||
typedef long long rmtS64;
|
||||
|
||||
|
||||
// Const, null-terminated string pointer
|
||||
typedef const char* rmtPStr;
|
||||
|
||||
|
||||
// Handle to the main remotery instance
|
||||
typedef struct Remotery Remotery;
|
||||
|
||||
|
||||
// All possible error codes
|
||||
typedef enum rmtError
|
||||
{
|
||||
RMT_ERROR_NONE,
|
||||
RMT_ERROR_RECURSIVE_SAMPLE, // Not an error but an internal message to calling code
|
||||
|
||||
// System errors
|
||||
RMT_ERROR_MALLOC_FAIL, // Malloc call within remotery failed
|
||||
RMT_ERROR_TLS_ALLOC_FAIL, // Attempt to allocate thread local storage failed
|
||||
RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL, // Failed to create a virtual memory mirror buffer
|
||||
RMT_ERROR_CREATE_THREAD_FAIL, // Failed to create a thread for the server
|
||||
|
||||
// Network TCP/IP socket errors
|
||||
RMT_ERROR_SOCKET_INIT_NETWORK_FAIL, // Network initialisation failure (e.g. on Win32, WSAStartup fails)
|
||||
RMT_ERROR_SOCKET_CREATE_FAIL, // Can't create a socket for connection to the remote viewer
|
||||
RMT_ERROR_SOCKET_BIND_FAIL, // Can't bind a socket for the server
|
||||
RMT_ERROR_SOCKET_LISTEN_FAIL, // Created server socket failed to enter a listen state
|
||||
RMT_ERROR_SOCKET_SET_NON_BLOCKING_FAIL, // Created server socket failed to switch to a non-blocking state
|
||||
RMT_ERROR_SOCKET_INVALID_POLL, // Poll attempt on an invalid socket
|
||||
RMT_ERROR_SOCKET_SELECT_FAIL, // Server failed to call select on socket
|
||||
RMT_ERROR_SOCKET_POLL_ERRORS, // Poll notified that the socket has errors
|
||||
RMT_ERROR_SOCKET_ACCEPT_FAIL, // Server failed to accept connection from client
|
||||
RMT_ERROR_SOCKET_SEND_TIMEOUT, // Timed out trying to send data
|
||||
RMT_ERROR_SOCKET_SEND_FAIL, // Unrecoverable error occured while client/server tried to send data
|
||||
RMT_ERROR_SOCKET_RECV_NO_DATA, // No data available when attempting a receive
|
||||
RMT_ERROR_SOCKET_RECV_TIMEOUT, // Timed out trying to receive data
|
||||
RMT_ERROR_SOCKET_RECV_FAILED, // Unrecoverable error occured while client/server tried to receive data
|
||||
|
||||
// WebSocket errors
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_NOT_GET, // WebSocket server handshake failed, not HTTP GET
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_VERSION, // WebSocket server handshake failed, can't locate WebSocket version
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_VERSION, // WebSocket server handshake failed, unsupported WebSocket version
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_HOST, // WebSocket server handshake failed, can't locate host
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_HOST, // WebSocket server handshake failed, host is not allowed to connect
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_KEY, // WebSocket server handshake failed, can't locate WebSocket key
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_KEY, // WebSocket server handshake failed, WebSocket key is ill-formed
|
||||
RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL, // WebSocket server handshake failed, internal error, bad string code
|
||||
RMT_ERROR_WEBSOCKET_DISCONNECTED, // WebSocket server received a disconnect request and closed the socket
|
||||
RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER, // Couldn't parse WebSocket frame header
|
||||
RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER_SIZE, // Partially received wide frame header size
|
||||
RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER_MASK, // Partially received frame header data mask
|
||||
RMT_ERROR_WEBSOCKET_RECEIVE_TIMEOUT, // Timeout receiving frame header
|
||||
|
||||
RMT_ERROR_REMOTERY_NOT_CREATED, // Remotery object has not been created
|
||||
RMT_ERROR_SEND_ON_INCOMPLETE_PROFILE, // An attempt was made to send an incomplete profile tree to the client
|
||||
|
||||
// CUDA error messages
|
||||
RMT_ERROR_CUDA_DEINITIALIZED, // This indicates that the CUDA driver is in the process of shutting down
|
||||
RMT_ERROR_CUDA_NOT_INITIALIZED, // This indicates that the CUDA driver has not been initialized with cuInit() or that initialization has failed
|
||||
RMT_ERROR_CUDA_INVALID_CONTEXT, // This most frequently indicates that there is no context bound to the current thread
|
||||
RMT_ERROR_CUDA_INVALID_VALUE, // This indicates that one or more of the parameters passed to the API call is not within an acceptable range of values
|
||||
RMT_ERROR_CUDA_INVALID_HANDLE, // This indicates that a resource handle passed to the API call was not valid
|
||||
RMT_ERROR_CUDA_OUT_OF_MEMORY, // The API call failed because it was unable to allocate enough memory to perform the requested operation
|
||||
RMT_ERROR_ERROR_NOT_READY, // This indicates that a resource handle passed to the API call was not valid
|
||||
|
||||
// Direct3D 11 error messages
|
||||
RMT_ERROR_D3D11_FAILED_TO_CREATE_QUERY, // Failed to create query for sample
|
||||
|
||||
// OpenGL error messages
|
||||
RMT_ERROR_OPENGL_ERROR, // Generic OpenGL error, no need to expose detail since app will need an OpenGL error callback registered
|
||||
|
||||
RMT_ERROR_CUDA_UNKNOWN,
|
||||
} rmtError;
|
||||
|
||||
|
||||
typedef enum rmtSampleFlags
|
||||
{
|
||||
// Default behaviour
|
||||
RMTSF_None = 0,
|
||||
|
||||
// Search parent for same-named samples and merge timing instead of adding a new sample
|
||||
RMTSF_Aggregate = 1,
|
||||
|
||||
// Merge sample with parent if it's the same sample
|
||||
RMTSF_Recursive = 2,
|
||||
} rmtSampleFlags;
|
||||
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
Public Interface
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// Can call remotery functions on a null pointer
|
||||
// TODO: Can embed extern "C" in these macros?
|
||||
|
||||
#define rmt_Settings() \
|
||||
RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_Settings(), NULL )
|
||||
|
||||
#define rmt_CreateGlobalInstance(rmt) \
|
||||
RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_CreateGlobalInstance(rmt), RMT_ERROR_NONE)
|
||||
|
||||
#define rmt_DestroyGlobalInstance(rmt) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_DestroyGlobalInstance(rmt))
|
||||
|
||||
#define rmt_SetGlobalInstance(rmt) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_SetGlobalInstance(rmt))
|
||||
|
||||
#define rmt_GetGlobalInstance() \
|
||||
RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_GetGlobalInstance(), NULL)
|
||||
|
||||
#define rmt_SetCurrentThreadName(rmt) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_SetCurrentThreadName(rmt))
|
||||
|
||||
#define rmt_LogText(text) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_LogText(text))
|
||||
|
||||
#define rmt_BeginCPUSample(name, flags) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, { \
|
||||
static rmtU32 rmt_sample_hash_##name = 0; \
|
||||
_rmt_BeginCPUSample(#name, flags, &rmt_sample_hash_##name); \
|
||||
})
|
||||
|
||||
#define rmt_BeginCPUSampleDynamic(namestr, flags) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_BeginCPUSample(namestr, flags, NULL))
|
||||
|
||||
#define rmt_EndCPUSample() \
|
||||
RMT_OPTIONAL(RMT_ENABLED, _rmt_EndCPUSample())
|
||||
|
||||
|
||||
// Callback function pointer types
|
||||
typedef void* (*rmtMallocPtr)(void* mm_context, rmtU32 size);
|
||||
typedef void* (*rmtReallocPtr)(void* mm_context, void* ptr, rmtU32 size);
|
||||
typedef void (*rmtFreePtr)(void* mm_context, void* ptr);
|
||||
typedef void (*rmtInputHandlerPtr)(const char* text, void* context);
|
||||
|
||||
|
||||
// Struture to fill in to modify Remotery default settings
|
||||
typedef struct rmtSettings
|
||||
{
|
||||
// Which port to listen for incoming connections on
|
||||
rmtU16 port;
|
||||
|
||||
// When this server exits it can leave the port open in TIME_WAIT state for
|
||||
// a while. This forces subsequent server bind attempts to fail when
|
||||
// restarting. If you find restarts fail repeatedly with bind attempts, set
|
||||
// this to true to forcibly reuse the open port.
|
||||
rmtBool reuse_open_port;
|
||||
|
||||
// Only allow connections on localhost?
|
||||
// For dev builds you may want to access your game from other devices but if
|
||||
// you distribute a game to your players with Remotery active, probably best
|
||||
// to limit connections to localhost.
|
||||
rmtBool limit_connections_to_localhost;
|
||||
|
||||
// How long to sleep between server updates, hopefully trying to give
|
||||
// a little CPU back to other threads.
|
||||
rmtU32 msSleepBetweenServerUpdates;
|
||||
|
||||
// Size of the internal message queues Remotery uses
|
||||
// Will be rounded to page granularity of 64k
|
||||
rmtU32 messageQueueSizeInBytes;
|
||||
|
||||
// If the user continuously pushes to the message queue, the server network
|
||||
// code won't get a chance to update unless there's an upper-limit on how
|
||||
// many messages can be consumed per loop.
|
||||
rmtU32 maxNbMessagesPerUpdate;
|
||||
|
||||
// Callback pointers for memory allocation
|
||||
rmtMallocPtr malloc;
|
||||
rmtReallocPtr realloc;
|
||||
rmtFreePtr free;
|
||||
void* mm_context;
|
||||
|
||||
// Callback pointer for receiving input from the Remotery console
|
||||
rmtInputHandlerPtr input_handler;
|
||||
|
||||
// Context pointer that gets sent to Remotery console callback function
|
||||
void* input_handler_context;
|
||||
|
||||
rmtPStr logFilename;
|
||||
} rmtSettings;
|
||||
|
||||
|
||||
// Structure to fill in when binding CUDA to Remotery
|
||||
typedef struct rmtCUDABind
|
||||
{
|
||||
// The main context that all driver functions apply before each call
|
||||
void* context;
|
||||
|
||||
// Driver API function pointers that need to be pointed to
|
||||
// Untyped so that the CUDA headers are not required in this file
|
||||
// NOTE: These are named differently to the CUDA functions because the CUDA API has a habit of using
|
||||
// macros to point function calls to different versions, e.g. cuEventDestroy is a macro for
|
||||
// cuEventDestroy_v2.
|
||||
void* CtxSetCurrent;
|
||||
void* CtxGetCurrent;
|
||||
void* EventCreate;
|
||||
void* EventDestroy;
|
||||
void* EventRecord;
|
||||
void* EventQuery;
|
||||
void* EventElapsedTime;
|
||||
|
||||
} rmtCUDABind;
|
||||
|
||||
|
||||
// Call once after you've initialised CUDA to bind it to Remotery
|
||||
#define rmt_BindCUDA(bind) \
|
||||
RMT_OPTIONAL(RMT_USE_CUDA, _rmt_BindCUDA(bind))
|
||||
|
||||
// Mark the beginning of a CUDA sample on the specified asynchronous stream
|
||||
#define rmt_BeginCUDASample(name, stream) \
|
||||
RMT_OPTIONAL(RMT_USE_CUDA, { \
|
||||
static rmtU32 rmt_sample_hash_##name = 0; \
|
||||
_rmt_BeginCUDASample(#name, &rmt_sample_hash_##name, stream); \
|
||||
})
|
||||
|
||||
// Mark the end of a CUDA sample on the specified asynchronous stream
|
||||
#define rmt_EndCUDASample(stream) \
|
||||
RMT_OPTIONAL(RMT_USE_CUDA, _rmt_EndCUDASample(stream))
|
||||
|
||||
|
||||
#define rmt_BindD3D11(device, context) \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, _rmt_BindD3D11(device, context))
|
||||
|
||||
#define rmt_UnbindD3D11() \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, _rmt_UnbindD3D11())
|
||||
|
||||
#define rmt_BeginD3D11Sample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, { \
|
||||
static rmtU32 rmt_sample_hash_##name = 0; \
|
||||
_rmt_BeginD3D11Sample(#name, &rmt_sample_hash_##name); \
|
||||
})
|
||||
|
||||
#define rmt_BeginD3D11SampleDynamic(namestr) \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, _rmt_BeginD3D11Sample(namestr, NULL))
|
||||
|
||||
#define rmt_EndD3D11Sample() \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, _rmt_EndD3D11Sample())
|
||||
|
||||
|
||||
#define rmt_BindOpenGL() \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_BindOpenGL())
|
||||
|
||||
#define rmt_UnbindOpenGL() \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_UnbindOpenGL())
|
||||
|
||||
#define rmt_BeginOpenGLSample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, { \
|
||||
static rmtU32 rmt_sample_hash_##name = 0; \
|
||||
_rmt_BeginOpenGLSample(#name, &rmt_sample_hash_##name); \
|
||||
})
|
||||
|
||||
#define rmt_BeginOpenGLSampleDynamic(namestr) \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_BeginOpenGLSample(namestr, NULL))
|
||||
|
||||
#define rmt_EndOpenGLSample() \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_EndOpenGLSample())
|
||||
|
||||
|
||||
#define rmt_BindMetal(command_buffer) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_BindMetal(command_buffer));
|
||||
|
||||
#define rmt_UnbindMetal() \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_UnbindMetal());
|
||||
|
||||
#define rmt_BeginMetalSample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, { \
|
||||
static rmtU32 rmt_sample_hash_##name = 0; \
|
||||
_rmt_BeginMetalSample(#name, &rmt_sample_hash_##name); \
|
||||
})
|
||||
|
||||
#define rmt_BeginMetalSampleDynamic(namestr) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_BeginMetalSample(namestr, NULL))
|
||||
|
||||
#define rmt_EndMetalSample() \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, _rmt_EndMetalSample())
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
C++ Public Interface Extensions
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
|
||||
#if RMT_ENABLED
|
||||
|
||||
// Types that end samples in their destructors
|
||||
extern "C" RMT_API void _rmt_EndCPUSample(void);
|
||||
struct rmt_EndCPUSampleOnScopeExit
|
||||
{
|
||||
~rmt_EndCPUSampleOnScopeExit()
|
||||
{
|
||||
_rmt_EndCPUSample();
|
||||
}
|
||||
};
|
||||
#if RMT_USE_CUDA
|
||||
extern "C" RMT_API void _rmt_EndCUDASample(void* stream);
|
||||
struct rmt_EndCUDASampleOnScopeExit
|
||||
{
|
||||
rmt_EndCUDASampleOnScopeExit(void* stream) : stream(stream)
|
||||
{
|
||||
}
|
||||
~rmt_EndCUDASampleOnScopeExit()
|
||||
{
|
||||
_rmt_EndCUDASample(stream);
|
||||
}
|
||||
void* stream;
|
||||
};
|
||||
#endif
|
||||
#if RMT_USE_D3D11
|
||||
extern "C" RMT_API void _rmt_EndD3D11Sample(void);
|
||||
struct rmt_EndD3D11SampleOnScopeExit
|
||||
{
|
||||
~rmt_EndD3D11SampleOnScopeExit()
|
||||
{
|
||||
_rmt_EndD3D11Sample();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if RMT_USE_OPENGL
|
||||
extern "C" RMT_API void _rmt_EndOpenGLSample(void);
|
||||
struct rmt_EndOpenGLSampleOnScopeExit
|
||||
{
|
||||
~rmt_EndOpenGLSampleOnScopeExit()
|
||||
{
|
||||
_rmt_EndOpenGLSample();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if RMT_USE_METAL
|
||||
extern "C" RMT_API void _rmt_EndMetalSample(void);
|
||||
struct rmt_EndMetalSampleOnScopeExit
|
||||
{
|
||||
~rmt_EndMetalSampleOnScopeExit()
|
||||
{
|
||||
_rmt_EndMetalSample();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// Pairs a call to rmt_Begin<TYPE>Sample with its call to rmt_End<TYPE>Sample when leaving scope
|
||||
#define rmt_ScopedCPUSample(name, flags) \
|
||||
RMT_OPTIONAL(RMT_ENABLED, rmt_BeginCPUSample(name, flags)); \
|
||||
RMT_OPTIONAL(RMT_ENABLED, rmt_EndCPUSampleOnScopeExit rmt_ScopedCPUSample##name);
|
||||
#define rmt_ScopedCUDASample(name, stream) \
|
||||
RMT_OPTIONAL(RMT_USE_CUDA, rmt_BeginCUDASample(name, stream)); \
|
||||
RMT_OPTIONAL(RMT_USE_CUDA, rmt_EndCUDASampleOnScopeExit rmt_ScopedCUDASample##name(stream));
|
||||
#define rmt_ScopedD3D11Sample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, rmt_BeginD3D11Sample(name)); \
|
||||
RMT_OPTIONAL(RMT_USE_D3D11, rmt_EndD3D11SampleOnScopeExit rmt_ScopedD3D11Sample##name);
|
||||
#define rmt_ScopedOpenGLSample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, rmt_BeginOpenGLSample(name)); \
|
||||
RMT_OPTIONAL(RMT_USE_OPENGL, rmt_EndOpenGLSampleOnScopeExit rmt_ScopedOpenGLSample##name);
|
||||
#define rmt_ScopedMetalSample(name) \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, rmt_BeginMetalSample(name)); \
|
||||
RMT_OPTIONAL(RMT_USE_METAL, rmt_EndMetalSampleOnScopeExit rmt_ScopedMetalSample##name);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
Private Interface - don't directly call these
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#if RMT_ENABLED
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
RMT_API rmtSettings* _rmt_Settings( void );
|
||||
RMT_API enum rmtError _rmt_CreateGlobalInstance(Remotery** remotery);
|
||||
RMT_API void _rmt_DestroyGlobalInstance(Remotery* remotery);
|
||||
RMT_API void _rmt_SetGlobalInstance(Remotery* remotery);
|
||||
RMT_API Remotery* _rmt_GetGlobalInstance(void);
|
||||
RMT_API void _rmt_SetCurrentThreadName(rmtPStr thread_name);
|
||||
RMT_API void _rmt_LogText(rmtPStr text);
|
||||
RMT_API void _rmt_BeginCPUSample(rmtPStr name, rmtU32 flags, rmtU32* hash_cache);
|
||||
RMT_API void _rmt_EndCPUSample(void);
|
||||
|
||||
#if RMT_USE_CUDA
|
||||
RMT_API void _rmt_BindCUDA(const rmtCUDABind* bind);
|
||||
RMT_API void _rmt_BeginCUDASample(rmtPStr name, rmtU32* hash_cache, void* stream);
|
||||
RMT_API void _rmt_EndCUDASample(void* stream);
|
||||
#endif
|
||||
|
||||
#if RMT_USE_D3D11
|
||||
RMT_API void _rmt_BindD3D11(void* device, void* context);
|
||||
RMT_API void _rmt_UnbindD3D11(void);
|
||||
RMT_API void _rmt_BeginD3D11Sample(rmtPStr name, rmtU32* hash_cache);
|
||||
RMT_API void _rmt_EndD3D11Sample(void);
|
||||
#endif
|
||||
|
||||
#if RMT_USE_OPENGL
|
||||
RMT_API void _rmt_BindOpenGL();
|
||||
RMT_API void _rmt_UnbindOpenGL(void);
|
||||
RMT_API void _rmt_BeginOpenGLSample(rmtPStr name, rmtU32* hash_cache);
|
||||
RMT_API void _rmt_EndOpenGLSample(void);
|
||||
#endif
|
||||
|
||||
#if RMT_USE_METAL
|
||||
RMT_API void _rmt_BeginMetalSample(rmtPStr name, rmtU32* hash_cache);
|
||||
RMT_API void _rmt_EndMetalSample(void);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#if RMT_USE_METAL
|
||||
#ifdef __OBJC__
|
||||
RMT_API void _rmt_BindMetal(id command_buffer);
|
||||
RMT_API void _rmt_UnbindMetal();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // RMT_ENABLED
|
||||
|
||||
|
||||
#endif
|
|
@ -1,59 +0,0 @@
|
|||
//
|
||||
// Copyright 2014-2018 Celtoys Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <Foundation/NSThread.h>
|
||||
#include <Foundation/NSDictionary.h>
|
||||
#include <Foundation/NSString.h>
|
||||
|
||||
#import <Metal/Metal.h>
|
||||
|
||||
// Store command buffer in thread-local so that each thread can point to its own
|
||||
static void SetCommandBuffer(id command_buffer)
|
||||
{
|
||||
NSMutableDictionary* thread_data = [[NSThread currentThread] threadDictionary];
|
||||
thread_data[@"rmtMTLCommandBuffer"] = command_buffer;
|
||||
}
|
||||
|
||||
static id GetCommandBuffer()
|
||||
{
|
||||
NSMutableDictionary* thread_data = [[NSThread currentThread] threadDictionary];
|
||||
return thread_data[@"rmtMTLCommandBuffer"];
|
||||
}
|
||||
|
||||
extern "C" void _rmt_BindMetal(id command_buffer)
|
||||
{
|
||||
SetCommandBuffer(command_buffer);
|
||||
}
|
||||
|
||||
extern "C" void _rmt_UnbindMetal()
|
||||
{
|
||||
SetCommandBuffer(0);
|
||||
}
|
||||
|
||||
// Needs to be in the same lib for this to work
|
||||
extern "C" unsigned long long rmtMetal_usGetTime();
|
||||
|
||||
static void SetTimestamp(void* data)
|
||||
{
|
||||
*((unsigned long long*)data) = rmtMetal_usGetTime();
|
||||
}
|
||||
|
||||
extern "C" void rmtMetal_MeasureCommandBuffer(unsigned long long* out_start, unsigned long long* out_end, unsigned int* out_ready)
|
||||
{
|
||||
id command_buffer = GetCommandBuffer();
|
||||
[command_buffer addScheduledHandler:^(id <MTLCommandBuffer>){ SetTimestamp(out_start); }];
|
||||
[command_buffer addCompletedHandler:^(id <MTLCommandBuffer>){ SetTimestamp(out_end); *out_ready = 1; }];
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
Remotery
|
||||
--------
|
||||
|
||||
[![Build Status](https://travis-ci.org/Celtoys/Remotery.svg?branch=master)](https://travis-ci.org/Celtoys/Remotery)
|
||||
[![Build status](https://ci.appveyor.com/api/projects/status/d1o8620mws9ihbsd?svg=true)](https://ci.appveyor.com/project/Celtoys/remotery)
|
||||
|
||||
A realtime CPU/GPU profiler hosted in a single C file with a viewer that runs in a web browser.
|
||||
|
||||
![screenshot](screenshot.png?raw=true)
|
||||
|
||||
Supported Platforms:
|
||||
|
||||
* Windows
|
||||
* Windows UWP (Hololens)
|
||||
* Linux
|
||||
* OSX
|
||||
* iOS
|
||||
* Android
|
||||
* XBox One
|
||||
* FreeBSD
|
||||
|
||||
Supported GPU Profiling APIS:
|
||||
|
||||
* D3D 11
|
||||
* OpenGL
|
||||
* CUDA
|
||||
* Metal
|
||||
|
||||
Features:
|
||||
|
||||
* Lightweight instrumentation of multiple threads running on the CPU.
|
||||
* Web viewer that runs in Chrome, Firefox and Safari. Custom WebSockets server
|
||||
transmits sample data to the browser on a latent thread.
|
||||
* Profiles itself and shows how it's performing in the viewer.
|
||||
* Console output for logging text.
|
||||
* Console input for sending commands to your game.
|
||||
|
||||
|
||||
Compiling
|
||||
---------
|
||||
|
||||
* Windows (MSVC) - add lib/Remotery.c and lib/Remotery.h to your program. Set include
|
||||
directories to add Remotery/lib path. The required library ws2_32.lib should be picked
|
||||
up through the use of the #pragma comment(lib, "ws2_32.lib") directive in Remotery.c.
|
||||
|
||||
* Mac OS X (XCode) - simply add lib/Remotery.c, lib/Remotery.h and lib/Remotery.mm to your program.
|
||||
|
||||
* Linux (GCC) - add the source in lib folder. Compilation of the code requires -pthreads for
|
||||
library linkage. For example to compile the same run: cc lib/Remotery.c sample/sample.c
|
||||
-I lib -pthread -lm
|
||||
|
||||
* FreeBSD - the easiest way is to take a look at the official port
|
||||
([devel/remotery](https://www.freshports.org/devel/remotery/)) and modify the port's
|
||||
Makefile if needed. There is also a package available via `pkg install remotery`.
|
||||
|
||||
You can define some extra macros to modify what features are compiled into Remotery:
|
||||
|
||||
Macro Default Description
|
||||
|
||||
RMT_ENABLED 1 Disable this to not include any bits of Remotery in your build
|
||||
RMT_USE_TINYCRT 0 Used by the Celtoys TinyCRT library (not released yet)
|
||||
RMT_USE_CUDA 0 Assuming CUDA headers/libs are setup, allow CUDA profiling
|
||||
RMT_USE_D3D11 0 Assuming Direct3D 11 headers/libs are setup, allow D3D11 GPU profiling
|
||||
RMT_USE_OPENGL 0 Allow OpenGL GPU profiling (dynamically links OpenGL libraries on available platforms)
|
||||
RMT_USE_METAL 0 Allow Metal profiling of command buffers
|
||||
|
||||
|
||||
Basic Use
|
||||
---------
|
||||
|
||||
See the sample directory for further examples. A quick example:
|
||||
|
||||
int main()
|
||||
{
|
||||
// Create the main instance of Remotery.
|
||||
// You need only do this once per program.
|
||||
Remotery* rmt;
|
||||
rmt_CreateGlobalInstance(&rmt);
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginCPUSample(LogText, 0);
|
||||
rmt_LogText("Time me, please!");
|
||||
rmt_EndCPUSample();
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedCPUSample(LogText, 0);
|
||||
rmt_LogText("Time me, too!");
|
||||
}
|
||||
|
||||
// Destroy the main instance of Remotery.
|
||||
rmt_DestroyGlobalInstance(rmt);
|
||||
}
|
||||
|
||||
|
||||
Running the Viewer
|
||||
------------------
|
||||
|
||||
Double-click or launch `vis/index.html` from the browser.
|
||||
|
||||
|
||||
Sampling CUDA GPU activity
|
||||
--------------------------
|
||||
|
||||
Remotery allows for profiling multiple threads of CUDA execution using different asynchronous streams
|
||||
that must all share the same context. After initialising both Remotery and CUDA you need to bind the
|
||||
two together using the call:
|
||||
|
||||
rmtCUDABind bind;
|
||||
bind.context = m_Context;
|
||||
bind.CtxSetCurrent = &cuCtxSetCurrent;
|
||||
bind.CtxGetCurrent = &cuCtxGetCurrent;
|
||||
bind.EventCreate = &cuEventCreate;
|
||||
bind.EventDestroy = &cuEventDestroy;
|
||||
bind.EventRecord = &cuEventRecord;
|
||||
bind.EventQuery = &cuEventQuery;
|
||||
bind.EventElapsedTime = &cuEventElapsedTime;
|
||||
rmt_BindCUDA(&bind);
|
||||
|
||||
Explicitly pointing to the CUDA interface allows Remotery to be included anywhere in your project without
|
||||
need for you to link with the required CUDA libraries. After the bind completes you can safely sample any
|
||||
CUDA activity:
|
||||
|
||||
CUstream stream;
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginCUDASample(UnscopedSample, stream);
|
||||
// ... CUDA code ...
|
||||
rmt_EndCUDASample(stream);
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedCUDASample(ScopedSample, stream);
|
||||
// ... CUDA code ...
|
||||
}
|
||||
|
||||
Remotery supports only one context for all threads and will use cuCtxGetCurrent and cuCtxSetCurrent to
|
||||
ensure the current thread has the context you specify in rmtCUDABind.context.
|
||||
|
||||
|
||||
Sampling Direct3D 11 GPU activity
|
||||
---------------------------------
|
||||
|
||||
Remotery allows sampling of D3D11 GPU activity on multiple devices on multiple threads. After initialising Remotery, you need to bind it to D3D11 with a single call from the thread that owns the device context:
|
||||
|
||||
// Parameters are ID3D11Device* and ID3D11DeviceContext*
|
||||
rmt_BindD3D11(d3d11_device, d3d11_context);
|
||||
|
||||
Sampling is then a simple case of:
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginD3D11Sample(UnscopedSample);
|
||||
// ... D3D code ...
|
||||
rmt_EndD3D11Sample();
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedD3D11Sample(ScopedSample);
|
||||
// ... D3D code ...
|
||||
}
|
||||
|
||||
Subsequent sampling calls from the same thread will use that device/context combination. When you shutdown your D3D11 device and context, ensure you notify Remotery before shutting down Remotery itself:
|
||||
|
||||
rmt_UnbindD3D11();
|
||||
|
||||
|
||||
Sampling OpenGL GPU activity
|
||||
----------------------------
|
||||
|
||||
Remotery allows sampling of GPU activity on your main OpenGL context. After initialising Remotery, you need
|
||||
to bind it to OpenGL with the single call:
|
||||
|
||||
rmt_BindOpenGL();
|
||||
|
||||
Sampling is then a simple case of:
|
||||
|
||||
// Explicit begin/end for C
|
||||
{
|
||||
rmt_BeginOpenGLSample(UnscopedSample);
|
||||
// ... OpenGL code ...
|
||||
rmt_EndOpenGLSample();
|
||||
}
|
||||
|
||||
// Scoped begin/end for C++
|
||||
{
|
||||
rmt_ScopedOpenGLSample(ScopedSample);
|
||||
// ... OpenGL code ...
|
||||
}
|
||||
|
||||
Support for multiple contexts can be added pretty easily if there is demand for the feature. When you shutdown
|
||||
your OpenGL device and context, ensure you notify Remotery before shutting down Remotery itself:
|
||||
|
||||
rmt_UnbindOpenGL();
|
||||
|
||||
|
||||
Sampling Metal GPU activity
|
||||
---------------------------
|
||||
|
||||
Remotery can sample Metal command buffers issued to the GPU from multiple threads. As the Metal API does not
|
||||
support finer grained profiling, samples will return only the timing of the bound command buffer, irrespective
|
||||
of how many you issue. As such, make sure you bind and sample the command buffer for each call site:
|
||||
|
||||
rmt_BindMetal(mtl_command_buffer);
|
||||
rmt_ScopedMetalSample(command_buffer_name);
|
||||
|
||||
The C API supports begin/end also:
|
||||
|
||||
rmt_BindMetal(mtl_command_buffer);
|
||||
rmt_BeginMetalSample(command_buffer_name);
|
||||
...
|
||||
rmt_EndMetalSample();
|
||||
|
||||
|
||||
Applying Configuration Settings
|
||||
-------------------------------
|
||||
|
||||
Before creating your Remotery instance, you can configure its behaviour by retrieving its settings object:
|
||||
|
||||
rmtSettings* settings = rmt_Settings();
|
||||
|
||||
Some important settings are:
|
||||
|
||||
// Redirect any Remotery allocations to your own malloc/free, with an additional context pointer
|
||||
// that gets passed to your callbacks.
|
||||
settings->malloc;
|
||||
settings->free;
|
||||
settings->mm_context;
|
||||
|
||||
// Specify an input handler that receives text input from the Remotery console, with an additional
|
||||
// context pointer that gets passed to your callback.
|
||||
// The handler will be called from the Remotery thread so synchronization with a mutex or atomics
|
||||
// might be needed to avoid race conditions with your threads.
|
||||
settings->input_handler;
|
||||
settings->input_handler_context;
|
|
@ -1,11 +0,0 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"typescript.tsdk": "C:\\Program Files (x86)\\Microsoft SDKs\\TypeScript\\2.0\\",
|
||||
"editor.fontSize": 13,
|
||||
"editor.lineHeight": 15,
|
||||
"editor.autoClosingBrackets": false,
|
||||
"editor.renderWhitespace": "all",
|
||||
"editor.quickSuggestions": true, // This disables string suggestions but ALSO disables local variables so keep it on
|
||||
"editor.wordBasedSuggestions": false, // Disable world-based suggestions being annoying in string editing (still some active)
|
||||
"editor.snippetSuggestions": "none" // This combined with above seems to completely eliminate string suggestions
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "0.1.0",
|
||||
"command": "tsc",
|
||||
"isShellCommand": true,
|
||||
"args": ["-p", "."],
|
||||
"showOutput": "silent",
|
||||
"problemMatcher": "$tsc"
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
|
||||
class DOMEvent
|
||||
{
|
||||
// Trigger for the DOM event
|
||||
Trigger: HTMLElement | Window;
|
||||
|
||||
// Event name
|
||||
EventName: string;
|
||||
|
||||
constructor(trigger: HTMLElement | Window, event_name: string)
|
||||
{
|
||||
this.Trigger = trigger;
|
||||
this.EventName = event_name;
|
||||
}
|
||||
|
||||
Subscribe(listener: EventListener) : void
|
||||
{
|
||||
this.Trigger.addEventListener(this.EventName, listener, false);
|
||||
}
|
||||
|
||||
Unsubscribe(listener: EventListener) : void
|
||||
{
|
||||
this.Trigger.removeEventListener(this.EventName, listener, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace DOM
|
||||
{
|
||||
export namespace Event
|
||||
{
|
||||
// Retrieves the event from the first parameter passed into an HTML event
|
||||
export function Get(event: MouseEvent) : MouseEvent
|
||||
{
|
||||
// Internet explorer doesn't pass the event
|
||||
return <MouseEvent>window.event || event;
|
||||
}
|
||||
|
||||
// Stops events bubbling up to parent event handlers
|
||||
export function StopPropagation(event: Event)
|
||||
{
|
||||
if (event)
|
||||
{
|
||||
event.cancelBubble = true;
|
||||
if (event.stopPropagation)
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop default action for event
|
||||
export function StopDefaultAction(event: Event)
|
||||
{
|
||||
if (event && event.preventDefault)
|
||||
event.preventDefault();
|
||||
else if (window.event && window.event.returnValue)
|
||||
window.event.returnValue = false;
|
||||
}
|
||||
|
||||
// Get the position of the mouse cursor, page relative
|
||||
export function GetMousePosition(event: MouseEvent) : int2
|
||||
{
|
||||
let e = Get(event);
|
||||
let p = new int2();
|
||||
if (e.pageX || e.pageY)
|
||||
{
|
||||
p.x = e.pageX;
|
||||
p.y = e.pageY;
|
||||
}
|
||||
else if (event.clientX || event.clientY)
|
||||
{
|
||||
p.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
|
||||
p.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}
|
||||
//export namespace Node
|
||||
{
|
||||
// Append an arbitrary block of HTML to an existing element
|
||||
/*export function AppendHTML(node: HTMLElement, html: string) : HTMLElement
|
||||
{
|
||||
var child = CreateHTML(html);
|
||||
node.appendChild(child);
|
||||
return child;
|
||||
}*/
|
||||
|
||||
// Append a div that clears the float style
|
||||
/*export function AppendClearFloat(node: HTMLElement) : HTMLElement
|
||||
{
|
||||
var child = document.createElement("div");
|
||||
child.style.clear = "both";
|
||||
node.appendChild(child);
|
||||
return child;
|
||||
}*/
|
||||
}
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
|
||||
namespace DOM
|
||||
{
|
||||
export class Node
|
||||
{
|
||||
// DOM element this object controls
|
||||
Element: HTMLElement;
|
||||
|
||||
// Optional DOM event handlers
|
||||
private _MouseDownEvent: DOMEvent;
|
||||
private _MouseUpEvent: DOMEvent;
|
||||
private _MouseMoveEvent: DOMEvent;
|
||||
private _ResizeEvent: DOMEvent;
|
||||
private _TouchStartEvent: DOMEvent;
|
||||
private _TouchEndEvent: DOMEvent;
|
||||
private _TouchCancelEvent: DOMEvent;
|
||||
private _TouchMoveEvent: DOMEvent;
|
||||
|
||||
|
||||
// ----- Constructor ---------------------------------------------------------------
|
||||
|
||||
|
||||
constructor(parameter: string | Element | Document | EventTarget)
|
||||
{
|
||||
// Take control of DOM objects
|
||||
if (parameter instanceof Element)
|
||||
{
|
||||
this.Element = <HTMLElement>parameter;
|
||||
}
|
||||
else if (parameter instanceof Document)
|
||||
{
|
||||
this.Element = <HTMLElement>parameter.documentElement;
|
||||
}
|
||||
else if (parameter instanceof EventTarget)
|
||||
{
|
||||
this.Element = <HTMLElement>parameter;
|
||||
}
|
||||
else if (typeof parameter === "string")
|
||||
{
|
||||
// Create a node from the provided HTML
|
||||
this.CreateFromHTML(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----- Properties ----------------------------------------------------------------
|
||||
|
||||
|
||||
// Absolute position of a HTML element on the page
|
||||
get AbsolutePosition() : int2
|
||||
{
|
||||
// Recurse up through parents, summing offsets from their parents
|
||||
let pos = new int2();
|
||||
for (let node = this.Element; node != null; node = <HTMLElement>node.offsetParent)
|
||||
{
|
||||
pos.x += node.offsetLeft;
|
||||
pos.y += node.offsetTop;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
set Position(position: int2)
|
||||
{
|
||||
this.Element.style.left = position.x.toString() + "px";
|
||||
this.Element.style.top = position.y.toString() + "px";
|
||||
}
|
||||
|
||||
// HTML element size, including borders and padding
|
||||
get Size() : int2
|
||||
{
|
||||
return new int2(this.Element.offsetWidth, this.Element.offsetHeight);
|
||||
}
|
||||
set Size(size: int2)
|
||||
{
|
||||
this.Element.style.width = size.x.toString() + "px";
|
||||
this.Element.style.height = size.y.toString() + "px";
|
||||
}
|
||||
|
||||
// Rendering z-index, applied through CSS
|
||||
get ZIndex() : number
|
||||
{
|
||||
if (this.Element.style.zIndex.length)
|
||||
return parseInt(this.Element.style.zIndex);
|
||||
return null;
|
||||
}
|
||||
set ZIndex(z_index: number)
|
||||
{
|
||||
this.Element.style.zIndex = z_index.toString();
|
||||
}
|
||||
|
||||
// Set HTML element opacity through CSS style
|
||||
set Opacity(value: number)
|
||||
{
|
||||
this.Element.style.opacity = value.toString();
|
||||
}
|
||||
|
||||
// Set HTML element colour through CSS style
|
||||
set Colour(colour: string)
|
||||
{
|
||||
this.Element.style.color = colour;
|
||||
}
|
||||
|
||||
set Cursor(cursor: string)
|
||||
{
|
||||
this.Element.style.cursor = cursor;
|
||||
}
|
||||
|
||||
// Return the parent HTML node
|
||||
get Parent() : Node
|
||||
{
|
||||
if (this.Element.parentElement)
|
||||
return new Node(this.Element.parentElement);
|
||||
return null;
|
||||
}
|
||||
get ParentElement() : HTMLElement
|
||||
{
|
||||
return this.Element.parentElement;
|
||||
}
|
||||
|
||||
|
||||
// ----- Methods -------------------------------------------------------------------
|
||||
|
||||
|
||||
// Check to see if a HTML element contains a class
|
||||
HasClass(class_name: string) : boolean
|
||||
{
|
||||
let regexp = new RegExp("\\b" + class_name + "\\b");
|
||||
return regexp.test(this.Element.className);
|
||||
}
|
||||
|
||||
// Remove a CSS class from a HTML element
|
||||
RemoveClass(class_name: string)
|
||||
{
|
||||
let regexp = new RegExp("\\b" + class_name + "\\b");
|
||||
this.Element.className = this.Element.className.replace(regexp, "");
|
||||
}
|
||||
|
||||
// Add a CSS class to a HTML element, specified last
|
||||
AddClass(class_name: string)
|
||||
{
|
||||
if (!this.HasClass(class_name))
|
||||
this.Element.className += " " + class_name;
|
||||
}
|
||||
|
||||
Find(filter: string) : Node
|
||||
{
|
||||
var element = this.Element.querySelector(filter);
|
||||
if (element)
|
||||
return new DOM.Node(element);
|
||||
return null;
|
||||
}
|
||||
|
||||
Append(node: Node)
|
||||
{
|
||||
this.Element.appendChild(node.Element);
|
||||
}
|
||||
|
||||
Detach()
|
||||
{
|
||||
if (this.Element.parentNode)
|
||||
this.Element.parentNode.removeChild(this.Element);
|
||||
}
|
||||
|
||||
Contains(node: Node) : boolean
|
||||
{
|
||||
while (node.Element != null && node.Element != this.Element)
|
||||
node = node.Parent;
|
||||
return node != null;
|
||||
}
|
||||
|
||||
SetText(text: string)
|
||||
{
|
||||
this.Element.textContent = text;
|
||||
}
|
||||
|
||||
// Create the HTML elements specified in the text parameter
|
||||
// Assumes there is only one root node in the text
|
||||
private CreateFromHTML(html: string)
|
||||
{
|
||||
// Prevent creation of superfluous text nodes
|
||||
html = html.trim();
|
||||
|
||||
// Create a temporary template to apply the HTML to
|
||||
let template = document.createElement("template");
|
||||
template.innerHTML = html;
|
||||
this.Element = <HTMLElement>template.content.firstElementChild;
|
||||
}
|
||||
|
||||
|
||||
// ----- Events --------------------------------------------------------------------
|
||||
|
||||
|
||||
// All event objects get created on-demand
|
||||
get MouseDownEvent() : DOMEvent
|
||||
{
|
||||
this._MouseDownEvent = this._MouseDownEvent || new DOMEvent(this.Element, "mousedown");
|
||||
return this._MouseDownEvent;
|
||||
}
|
||||
get MouseUpEvent() : DOMEvent
|
||||
{
|
||||
this._MouseUpEvent = this._MouseUpEvent || new DOMEvent(this.Element, "mouseup");
|
||||
return this._MouseUpEvent;
|
||||
}
|
||||
get MouseMoveEvent() : DOMEvent
|
||||
{
|
||||
this._MouseMoveEvent = this._MouseMoveEvent || new DOMEvent(this.Element, "mousemove");
|
||||
return this._MouseMoveEvent;
|
||||
}
|
||||
get ResizeEvent() : DOMEvent
|
||||
{
|
||||
this._ResizeEvent = this._ResizeEvent || new DOMEvent(window, "resize");
|
||||
return this._ResizeEvent;
|
||||
}
|
||||
get TouchStartEvent() : DOMEvent
|
||||
{
|
||||
this._TouchStartEvent = this._TouchStartEvent || new DOMEvent(this.Element, "touchstart");
|
||||
return this._TouchStartEvent;
|
||||
}
|
||||
get TouchEndEvent() : DOMEvent
|
||||
{
|
||||
this._TouchEndEvent = this._TouchEndEvent || new DOMEvent(this.Element, "touchend");
|
||||
return this._TouchEndEvent;
|
||||
}
|
||||
get TouchCancelEvent() : DOMEvent
|
||||
{
|
||||
this._TouchCancelEvent = this._TouchCancelEvent || new DOMEvent(this.Element, "touchcancel");
|
||||
return this._TouchCancelEvent;
|
||||
}
|
||||
get TouchMoveEvent() : DOMEvent
|
||||
{
|
||||
this._TouchMoveEvent = this._TouchMoveEvent || new DOMEvent(this.Element, "touchmove");
|
||||
return this._TouchMoveEvent;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function $(parameter: string | Element | Document | EventTarget)
|
||||
{
|
||||
if (typeof parameter == "string")
|
||||
return new DOM.Node(document.querySelector(parameter));
|
||||
|
||||
return new DOM.Node(parameter);
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
namespace Hash
|
||||
{
|
||||
export function Wang_U32(key: number) : number
|
||||
{
|
||||
key += ~(key << 15);
|
||||
key ^= (key >> 10);
|
||||
key += (key << 3);
|
||||
key ^= (key >> 6);
|
||||
key += ~(key << 11);
|
||||
key ^= (key >> 16);
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
export function Combine_U32(hash_a: number, hash_b: number) : number
|
||||
{
|
||||
let random_bits = 0x9E3779B9;
|
||||
hash_a ^= hash_b + random_bits + (hash_a << 6) + (hash_a >> 2);
|
||||
return hash_a;
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
|
||||
// Internal operations of int2 can assume inputs are already integer and avoid rounding
|
||||
enum int2Round
|
||||
{
|
||||
Do,
|
||||
Dont
|
||||
}
|
||||
|
||||
|
||||
class int2
|
||||
{
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
static readonly Zero = new int2(0, 0);
|
||||
static readonly One = new int2(1, 1);
|
||||
|
||||
constructor(x: number = 0, y: number = x, round: int2Round = int2Round.Do)
|
||||
{
|
||||
if (round == int2Round.Do)
|
||||
{
|
||||
this.x = Math.round(x);
|
||||
this.y = Math.round(y);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
Copy() : int2
|
||||
{
|
||||
return new int2(this.x, this.y, int2Round.Dont);
|
||||
}
|
||||
|
||||
static Add(a: int2, b: int2) : int2
|
||||
{
|
||||
return new int2(a.x + b.x, a.y + b.y, int2Round.Dont);
|
||||
}
|
||||
|
||||
static Sub(a: int2, b: int2) : int2
|
||||
{
|
||||
return new int2(a.x - b.x, a.y - b.y, int2Round.Dont);
|
||||
}
|
||||
|
||||
static Mul(a: int2, b: int2) : int2
|
||||
{
|
||||
return new int2(a.x * b.x, a.y * b.y, int2Round.Dont);
|
||||
}
|
||||
|
||||
static Min(a: int2, b: int2) : int2
|
||||
{
|
||||
return new int2(Math.min(a.x, b.x), Math.min(a.y, b.y), int2Round.Dont);
|
||||
}
|
||||
|
||||
static Max(a: int2, b: int2) : int2
|
||||
{
|
||||
return new int2(Math.max(a.x, b.x), Math.max(a.y, b.y), int2Round.Dont);
|
||||
}
|
||||
|
||||
static Min0(a: int2) : int2
|
||||
{
|
||||
return new int2(Math.min(a.x, 0), Math.min(a.y, 0), int2Round.Dont);
|
||||
}
|
||||
|
||||
static Max0(a: int2) : int2
|
||||
{
|
||||
return new int2(Math.max(a.x, 0), Math.max(a.y, 0), int2Round.Dont);
|
||||
}
|
||||
|
||||
static Neg(a: int2) : int2
|
||||
{
|
||||
return new int2(-a.x, -a.y, int2Round.Dont);
|
||||
}
|
||||
|
||||
static Abs(a: int2) : int2
|
||||
{
|
||||
return new int2(Math.abs(a.x), Math.abs(a.y), int2Round.Dont);
|
||||
}
|
||||
|
||||
static Equal(a: int2, b: int2) : boolean
|
||||
{
|
||||
if (a == null || b == null)
|
||||
return false;
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AABB
|
||||
{
|
||||
min: int2;
|
||||
max: int2;
|
||||
|
||||
constructor(min: int2, max: int2)
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
Expand(e: number) : void
|
||||
{
|
||||
let ev = new int2(e);
|
||||
this.min = int2.Sub(this.min, ev);
|
||||
this.max = int2.Add(this.max, ev);
|
||||
}
|
||||
|
||||
static Intersect(a: AABB, b: AABB) : boolean
|
||||
{
|
||||
return a.min.x < b.max.x && a.min.y < b.max.y && b.min.x < a.max.x && b.min.y < a.max.y;
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
|
||||
//
|
||||
// TODO:
|
||||
//
|
||||
// * Show windows with animation?
|
||||
// * Make root window that's embedded in the browser.
|
||||
// * When adding a control, give option to add as shown/hidden.
|
||||
// * Move all WindowManager functionality into Window and apply it to Controls.
|
||||
//
|
||||
|
||||
function TestAll() : WM.Container
|
||||
{
|
||||
let Container = new WM.Container(new int2(10, 10), new int2(1000, 800));
|
||||
Container.Show();
|
||||
|
||||
let WindowA = new WM.Window("Window A", new int2(10, 10), new int2(200, 200));
|
||||
WindowA.Title = "Window A Changed";
|
||||
Container.Add(WindowA);
|
||||
|
||||
WindowA.Add(new WM.Window("SubWindow 0 A", new int2(10, 10), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 B", new int2(20, 20), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 C", new int2(30, 30), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 D", new int2(40, 40), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 E", new int2(50, 50), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 F", new int2(60, 60), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 G", new int2(70, 70), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 H", new int2(80, 80), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 I", new int2(90, 90), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 J", new int2(100, 100), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 K", new int2(110, 110), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 L", new int2(120, 120), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 M", new int2(130, 130), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 N", new int2(140, 140), new int2(200, 200)));
|
||||
WindowA.Add(new WM.Window("SubWindow 0 O", new int2(150, 150), new int2(200, 200)));
|
||||
|
||||
Container.Add(new WM.Window("Window B", new int2(220, 10), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window C", new int2(430, 10), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window D", new int2(640, 10), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window E", new int2(10, 220), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window F", new int2(220, 220), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window G", new int2(430, 220), new int2(200, 200)));
|
||||
Container.Add(new WM.Window("Window H", new int2(640, 220), new int2(200, 200)));
|
||||
|
||||
let WindowI = new WM.Window("Window I", new int2(500, 400), new int2(300, 300));
|
||||
Container.Add(WindowI);
|
||||
WindowI.Add(new WM.Window("SubWindow 1 A", new int2(10, 10), new int2(289, 289)));
|
||||
WindowI.Add(new WM.Window("SubWindow 1 B", new int2(20, 20), new int2(289, 289)));
|
||||
WindowI.Add(new WM.Window("SubWindow 1 C", new int2(30, 30), new int2(289, 289)));
|
||||
WindowI.Add(new WM.Window("SubWindow 1 D", new int2(40, 40), new int2(289, 289)));
|
||||
WindowI.Add(new WM.Window("SubWindow 1 E", new int2(50, 50), new int2(289, 289)));
|
||||
|
||||
return Container;
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
export class Container extends Control
|
||||
{
|
||||
static TemplateHTML = "<div class='Container'></div>";
|
||||
|
||||
static SnapBorderSize = 5;
|
||||
|
||||
// List of controls contained by the window, in z-order
|
||||
Controls: Control[] = [];
|
||||
|
||||
// Connectivity graph for all controls in the container, allowing auto-anchor
|
||||
ControlGraph: ControlGraph = new ControlGraph();
|
||||
|
||||
// Sizing simulation for controls on each axis
|
||||
protected ControlSizerX: ControlSizer = new ControlSizer();
|
||||
protected ControlSizerY: ControlSizer = new ControlSizer();
|
||||
|
||||
constructor(position: int2, size: int2, node?: DOM.Node)
|
||||
{
|
||||
super(node ? node : new DOM.Node(Container.TemplateHTML), position, size);
|
||||
}
|
||||
|
||||
Add(control: Control) : Control
|
||||
{
|
||||
this.Controls.push(control);
|
||||
control.ParentContainer = this;
|
||||
control.Show();
|
||||
return control;
|
||||
}
|
||||
|
||||
Remove(control: Control)
|
||||
{
|
||||
control.Hide();
|
||||
|
||||
let index = this.Controls.indexOf(control);
|
||||
this.Controls.splice(index, 1);
|
||||
|
||||
control.ParentContainer = null;
|
||||
}
|
||||
|
||||
private UpdateZIndices()
|
||||
{
|
||||
// ZINDEX needs to be relative to parent!
|
||||
|
||||
// Set a CSS z-index for each visible control from the bottom-up
|
||||
for (let i = 0; i < this.Controls.length; i++)
|
||||
{
|
||||
let control = this.Controls[i];
|
||||
if (!control.Visible)
|
||||
continue;
|
||||
|
||||
// Ensure there's space between each window for the elements inside to be sorted
|
||||
let z = (i + 1) * 10;
|
||||
control.ZIndex = z;
|
||||
}
|
||||
}
|
||||
|
||||
SetTopControl(control: Control) : void
|
||||
{
|
||||
// Push the control to the end of the control list
|
||||
let index = this.Controls.indexOf(control);
|
||||
if (index != -1)
|
||||
{
|
||||
this.Controls.splice(index, 1);
|
||||
this.Controls.push(control);
|
||||
|
||||
// Recalculate z-indices for visible sort
|
||||
this.UpdateZIndices();
|
||||
}
|
||||
}
|
||||
|
||||
SetBottomControl(control: Control) : void
|
||||
{
|
||||
// Push the control to the start of the control list
|
||||
let index = this.Controls.indexOf(control);
|
||||
if (index != -1)
|
||||
{
|
||||
this.Controls.splice(index, 1);
|
||||
this.Controls.unshift(control);
|
||||
|
||||
// Recalculate z-indices for visible sort
|
||||
this.UpdateZIndices();
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the node which all controls added to the container are parented to
|
||||
get ControlParentNode() : DOM.Node
|
||||
{
|
||||
return this.Node;
|
||||
}
|
||||
|
||||
protected SetSize(size: int2) : void
|
||||
{
|
||||
// Set size on super and notify child controls
|
||||
super.SetSize(size);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
function GenerateID(position: int2, size: int2) : number
|
||||
{
|
||||
// Use initial placement of the container as a unique ID generator
|
||||
let a = Hash.Combine_U32(Hash.Wang_U32(position.x), Hash.Wang_U32(position.y));
|
||||
let b = Hash.Combine_U32(Hash.Wang_U32(size.x), Hash.Wang_U32(size.y));
|
||||
return Hash.Combine_U32(a, b);
|
||||
}
|
||||
|
||||
|
||||
export class Control
|
||||
{
|
||||
// Unique ID within a container's control list
|
||||
ID: number;
|
||||
|
||||
// Main generated HTML node for the control
|
||||
Node: DOM.Node;
|
||||
|
||||
// Parent container for the control; can be null in the case of the browser body
|
||||
private _ParentContainer: Container;
|
||||
|
||||
// Rectangle coverage
|
||||
// Always ensure position/size are defined as they are required to calculate bottom-right
|
||||
private _Position = new int2(0);
|
||||
private _Size = new int2(0);
|
||||
private _BottomRight = new int2(0);
|
||||
|
||||
// Records current visibility state - not for external writes
|
||||
private _Visible: boolean = false;
|
||||
|
||||
|
||||
// ----- Constructor ---------------------------------------------------------------
|
||||
|
||||
|
||||
constructor(node: DOM.Node, position: int2, size: int2)
|
||||
{
|
||||
this.ID = GenerateID(position, size);
|
||||
this.Node = node;
|
||||
this.Position = position;
|
||||
this.Size = size;
|
||||
|
||||
this.Node.MouseDownEvent.Subscribe(this.OnMouseDown);
|
||||
}
|
||||
|
||||
|
||||
// ----- Public API Properties -----------------------------------------------------
|
||||
|
||||
|
||||
// Cached node position
|
||||
set Position(position: int2)
|
||||
{
|
||||
this._Position = position;
|
||||
this.Node.Position = position;
|
||||
this._BottomRight = int2.Add(this._Position, this._Size);
|
||||
}
|
||||
get Position() : int2
|
||||
{
|
||||
return this._Position;
|
||||
}
|
||||
|
||||
// Cached node size
|
||||
// Size set in alternate implementation because you can't access
|
||||
// properties from 'super'
|
||||
protected SetSize(size: int2) : void
|
||||
{
|
||||
this._Size = size;
|
||||
this.Node.Size = size;
|
||||
this._BottomRight = int2.Add(this._Position, this._Size);
|
||||
}
|
||||
set Size(size: int2)
|
||||
{
|
||||
this.SetSize(size);
|
||||
}
|
||||
get Size() : int2
|
||||
{
|
||||
return this._Size;
|
||||
}
|
||||
|
||||
// Alternative rectangle coverage access
|
||||
set TopLeft(tl: int2)
|
||||
{
|
||||
let old_br = this._BottomRight.Copy();
|
||||
this.Position = tl;
|
||||
this.Size = int2.Sub(old_br, this.Position);
|
||||
}
|
||||
get TopLeft() : int2
|
||||
{
|
||||
return this._Position;
|
||||
}
|
||||
set BottomRight(br: int2)
|
||||
{
|
||||
this.SetSize(int2.Sub(br, this._Position));
|
||||
}
|
||||
get BottomRight() : int2
|
||||
{
|
||||
return this._BottomRight;
|
||||
}
|
||||
|
||||
// Tells whether the control thinks it's visible or not
|
||||
get Visible() : boolean
|
||||
{
|
||||
return this._Visible;
|
||||
}
|
||||
|
||||
|
||||
// ----- Internal API Properties ---------------------------------------------------
|
||||
|
||||
|
||||
// Set/Get control z-index
|
||||
set ZIndex(z_index: number)
|
||||
{
|
||||
this.Node.ZIndex = z_index;
|
||||
}
|
||||
get ZIndex() : number
|
||||
{
|
||||
return this.Node.ZIndex;
|
||||
}
|
||||
|
||||
// Set/Get the parent container for this control
|
||||
set ParentContainer(parent_container: Container)
|
||||
{
|
||||
if (this._ParentContainer == null)
|
||||
$(document.body).ResizeEvent.Unsubscribe(this.OnParentResize);
|
||||
|
||||
this._ParentContainer = parent_container;
|
||||
|
||||
if (this._ParentContainer == null)
|
||||
$(document.body).ResizeEvent.Subscribe(this.OnParentResize);
|
||||
}
|
||||
get ParentContainer() : Container
|
||||
{
|
||||
return this._ParentContainer;
|
||||
}
|
||||
|
||||
// Returns a node within the parent container that's designated for adding controls
|
||||
// Or document.body if there is no parent
|
||||
protected get ParentNode() : DOM.Node
|
||||
{
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container == null)
|
||||
return $(document.body);
|
||||
return parent_container.ControlParentNode;
|
||||
}
|
||||
|
||||
|
||||
// ----- Public API Methods --------------------------------------------------------
|
||||
|
||||
|
||||
Show() : void
|
||||
{
|
||||
// Add to parent node if not already there
|
||||
if (this.Node.Parent == null)
|
||||
{
|
||||
this.ParentNode.Append(this.Node);
|
||||
this._Visible = true;
|
||||
}
|
||||
}
|
||||
Hide() : void
|
||||
{
|
||||
if (this.Node.Parent != null)
|
||||
{
|
||||
this.Node.Detach();
|
||||
this._Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
SendToTop() : void
|
||||
{
|
||||
if (this._ParentContainer)
|
||||
this._ParentContainer.SetTopControl(this);
|
||||
}
|
||||
|
||||
SendToBottom() : void
|
||||
{
|
||||
if (this._ParentContainer)
|
||||
this._ParentContainer.SetBottomControl(this);
|
||||
}
|
||||
|
||||
|
||||
// ----- Internal API Methods ------------------------------------------------------
|
||||
|
||||
|
||||
OnParentResize = () =>
|
||||
{
|
||||
// TODO: Snap on show?
|
||||
}
|
||||
|
||||
private OnMouseDown = (event: MouseEvent) =>
|
||||
{
|
||||
// Allow bubble-up for this event so that it filters through nested windows
|
||||
this.SendToTop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,273 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
export class ControlRef
|
||||
{
|
||||
// Primary sort key, to be combined with Side
|
||||
FromIndex: number;
|
||||
|
||||
// Cached control reference to save needing to lookup in the parent
|
||||
From: Control;
|
||||
|
||||
// Which side of the control the reference is on
|
||||
Side: Side;
|
||||
|
||||
// Control this references
|
||||
ToIndex: number;
|
||||
To: Control;
|
||||
|
||||
|
||||
constructor(from_index: number, from: Control, side: Side, to_index: number, to: Control)
|
||||
{
|
||||
this.FromIndex = from_index;
|
||||
this.From = from;
|
||||
this.Side = side;
|
||||
this.ToIndex = to_index;
|
||||
this.To = to;
|
||||
}
|
||||
|
||||
get SortIndex() : number
|
||||
{
|
||||
return this.FromIndex * 4 + this.Side;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ControlRefInfo
|
||||
{
|
||||
// Storing this allows the class to query graph properties without the extra parameter
|
||||
ParentGraph: ControlGraph;
|
||||
|
||||
// Cached control reference to save needing to lookup in the parent
|
||||
Control: Control;
|
||||
|
||||
// Side specified for debug
|
||||
Side: Side;
|
||||
|
||||
// Links in the control ref array
|
||||
StartRef: number;
|
||||
NbRefs: number;
|
||||
|
||||
constructor(parent_graph: ControlGraph, control: Control, side: Side)
|
||||
{
|
||||
this.ParentGraph = parent_graph;
|
||||
this.Control = control;
|
||||
this.Side = side;
|
||||
this.StartRef = -1;
|
||||
this.NbRefs = 0;
|
||||
}
|
||||
|
||||
References(control: Control) : boolean
|
||||
{
|
||||
for (let i = 0; i < this.NbRefs; i++)
|
||||
{
|
||||
if (this.ParentGraph.Refs[this.StartRef + i].To == control)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
GetControlRef(index: number) : ControlRef
|
||||
{
|
||||
if (index < this.NbRefs)
|
||||
return this.ParentGraph.Refs[this.StartRef + index];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
GetSide(side: Side) : ControlRefInfo
|
||||
{
|
||||
if (this.NbRefs == 0)
|
||||
return null;
|
||||
|
||||
let ref = this.ParentGraph.Refs[this.StartRef];
|
||||
return this.ParentGraph.RefInfos[ref.FromIndex * 4 + side];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ControlGraph
|
||||
{
|
||||
Refs: ControlRef[] = [];
|
||||
|
||||
RefInfos: ControlRefInfo[] = [];
|
||||
|
||||
Build(container: Container)
|
||||
{
|
||||
// Clear existing references
|
||||
this.Refs = [ ];
|
||||
this.RefInfos = [ ];
|
||||
|
||||
// Mark all controls as unvisited
|
||||
let control_visited: boolean[] = [];
|
||||
for (let i = 0; i < container.Controls.length; i++)
|
||||
control_visited.push(false);
|
||||
|
||||
// Build references for each container
|
||||
for (let i = 0; i < container.Controls.length; i++)
|
||||
{
|
||||
if (control_visited[i])
|
||||
continue;
|
||||
|
||||
let control = container.Controls[i];
|
||||
|
||||
// TODO: Exempt Rulers but allow Buttons? Or, exempt any control from auto snap/anchor?
|
||||
// This is technically a container graph right now
|
||||
if (!(control instanceof Container))
|
||||
continue;
|
||||
|
||||
this.BuildRefs(control, container, control_visited);
|
||||
}
|
||||
|
||||
// Sort control references, packing controls/sides next to each other
|
||||
this.Refs.sort((a: ControlRef, b: ControlRef) : number =>
|
||||
{
|
||||
return a.SortIndex - b.SortIndex;
|
||||
});
|
||||
|
||||
// Initialise the control ref info array
|
||||
for (let i = 0; i < container.Controls.length * 4; i++)
|
||||
{
|
||||
let control = container.Controls[i >> 2];
|
||||
this.RefInfos.push(new ControlRefInfo(this, control, i & 3));
|
||||
}
|
||||
|
||||
// Tell each control where its reference list starts and ends
|
||||
let last_sort_index = -1;
|
||||
for (let i = 0; i < this.Refs.length; i++)
|
||||
{
|
||||
let ref = this.Refs[i];
|
||||
let sort_index = ref.SortIndex;
|
||||
let ref_info = this.RefInfos[sort_index];
|
||||
|
||||
if (last_sort_index != sort_index)
|
||||
{
|
||||
ref_info.StartRef = i;
|
||||
last_sort_index = sort_index;
|
||||
}
|
||||
|
||||
ref_info.NbRefs++;
|
||||
}
|
||||
}
|
||||
|
||||
private BuildRefs(root_control: Control, container: Container, control_visited: boolean[])
|
||||
{
|
||||
// First control to visit is the root control
|
||||
let to_visit_controls: Control[] = [ root_control ];
|
||||
|
||||
// Loop through any controls left to visit
|
||||
for (let control_0 of to_visit_controls)
|
||||
{
|
||||
// It's possible for the same container to be pushed onto the to-visit list more than once
|
||||
let control_0_index = container.Controls.indexOf(control_0);
|
||||
if (control_visited[control_0_index])
|
||||
continue;
|
||||
control_visited[control_0_index] = true;
|
||||
|
||||
let tl_0 = control_0.TopLeft;
|
||||
let br_0 = control_0.BottomRight;
|
||||
|
||||
// Add references to the parent container
|
||||
let b = Container.SnapBorderSize;
|
||||
let s = container.ControlParentNode.Size;
|
||||
if (tl_0.x <= b)
|
||||
this.Refs.push(new ControlRef(control_0_index, control_0, Side.Left, -1, container));
|
||||
if (tl_0.y <= b)
|
||||
this.Refs.push(new ControlRef(control_0_index, control_0, Side.Top, -1, container));
|
||||
if (br_0.x >= s.x - b)
|
||||
this.Refs.push(new ControlRef(control_0_index, control_0, Side.Right, -1, container));
|
||||
if (br_0.y >= s.y - b)
|
||||
this.Refs.push(new ControlRef(control_0_index, control_0, Side.Bottom, -1, container));
|
||||
|
||||
// Check candidate controls for auto-anchor intersection
|
||||
for (let control_1 of container.Controls)
|
||||
{
|
||||
// If a control has been previous visited, no need to add forward links as back
|
||||
// links would have been added when it was visited
|
||||
let control_1_index = container.Controls.indexOf(control_1);
|
||||
if (control_visited[control_1_index])
|
||||
continue;
|
||||
|
||||
// TODO: Exempt Rulers but allow Buttons? Or, exempt any control from auto snap/anchor?
|
||||
if (!(control_1 instanceof Container))
|
||||
continue;
|
||||
|
||||
let tl_1 = control_1.TopLeft;
|
||||
let br_1 = control_1.BottomRight;
|
||||
|
||||
let side_0 = Side.None;
|
||||
let side_1 = Side.None;
|
||||
|
||||
// Check for vertical separating axis
|
||||
if (tl_1.y - br_0.y < 0 && tl_0.y - br_1.y < 0)
|
||||
{
|
||||
// Check left/right edge intersection
|
||||
if (Math.abs(tl_0.x - br_1.x) < b)
|
||||
{
|
||||
side_0 = Side.Left;
|
||||
side_1 = Side.Right;
|
||||
}
|
||||
if (Math.abs(br_0.x - tl_1.x) < b)
|
||||
{
|
||||
side_0 = Side.Right;
|
||||
side_1 = Side.Left;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for horizontal separating axis
|
||||
if (tl_1.x - br_0.x < 0 && tl_0.x - br_1.x < 0)
|
||||
{
|
||||
// Check top/bottom edge intersection
|
||||
if (Math.abs(tl_0.y - br_1.y) < b)
|
||||
{
|
||||
side_0 = Side.Top;
|
||||
side_1 = Side.Bottom;
|
||||
}
|
||||
if (Math.abs(br_0.y - tl_1.y) < b)
|
||||
{
|
||||
side_0 = Side.Bottom;
|
||||
side_1 = Side.Top;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate references for any intersection
|
||||
if (side_0 != Side.None)
|
||||
{
|
||||
this.Refs.push(new ControlRef(control_0_index, control_0, side_0, control_1_index, control_1));
|
||||
this.Refs.push(new ControlRef(control_1_index, control_1, side_1, control_0_index, control_0));
|
||||
to_visit_controls.push(control_1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugLog()
|
||||
{
|
||||
console.log("\n--- DebugLog --------------------------------");
|
||||
|
||||
let x = Side[Side.Top];
|
||||
for (let ref_info of this.RefInfos)
|
||||
{
|
||||
if (!(ref_info.Control instanceof Container))
|
||||
continue;
|
||||
if (ref_info.NbRefs == 0)
|
||||
continue;
|
||||
|
||||
let names = "";
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
let window = this.Refs[ref_info.StartRef + i].To as Window;
|
||||
names += window.Title + ", ";
|
||||
}
|
||||
|
||||
console.log((<Window>ref_info.Control).Title, Side[ref_info.Side] + ": ", names);
|
||||
}
|
||||
|
||||
/*for (let ref of this.Refs)
|
||||
{
|
||||
console.log((<Window>ref.From).Title, ref.Side, (<Window>ref.To).Title);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,515 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
class Span
|
||||
{
|
||||
// Only for DebugLog
|
||||
Title: string;
|
||||
|
||||
// Source control for copying simulation back
|
||||
Control: Control;
|
||||
|
||||
Min: number;
|
||||
Max: number;
|
||||
|
||||
RestSizeStrength: number;
|
||||
SizeStrength: number;
|
||||
|
||||
// Number of controls between this one and the side of the container
|
||||
SideDistance: number;
|
||||
}
|
||||
|
||||
class SizeConstraint
|
||||
{
|
||||
Span: Span;
|
||||
Size: number;
|
||||
}
|
||||
|
||||
class ContainerConstraint
|
||||
{
|
||||
Span: Span;
|
||||
Side: Side;
|
||||
Position: number;
|
||||
}
|
||||
|
||||
class BufferConstraint
|
||||
{
|
||||
Span0: Span;
|
||||
Span1: Span;
|
||||
Side: Side;
|
||||
}
|
||||
|
||||
class SnapConstraint
|
||||
{
|
||||
MinSpan: Span;
|
||||
MaxSpan: Span;
|
||||
}
|
||||
|
||||
// TODO: Need to unify snapped controls into one constraint instead of multiple
|
||||
export class ControlSizer
|
||||
{
|
||||
// Allow the sizer to work independently on horizontal/vertical axes
|
||||
MinSide: Side;
|
||||
MaxSide: Side;
|
||||
|
||||
ContainerRestSize: number;
|
||||
ContainerSize: number;
|
||||
|
||||
Spans: Span[] = [];
|
||||
|
||||
ContainerConstraints: ContainerConstraint[] = [];
|
||||
BufferConstraints: BufferConstraint[] = [];
|
||||
SizeConstraints: SizeConstraint[] = [];
|
||||
SnapConstraints: SnapConstraint[] = [];
|
||||
|
||||
Clear()
|
||||
{
|
||||
this.Spans = [];
|
||||
this.ContainerConstraints = [];
|
||||
this.BufferConstraints = [];
|
||||
this.SizeConstraints = [];
|
||||
this.SnapConstraints = [];
|
||||
}
|
||||
|
||||
Build(base_side: Side, container: Container, control_graph: ControlGraph)
|
||||
{
|
||||
this.MinSide = base_side;
|
||||
this.MaxSide = base_side + 1;
|
||||
|
||||
if (base_side == Side.Left)
|
||||
this.ContainerRestSize = container.ControlParentNode.Size.x;
|
||||
else
|
||||
this.ContainerRestSize = container.ControlParentNode.Size.y;
|
||||
|
||||
// Clear previous constraints
|
||||
this.Clear();
|
||||
|
||||
// Build the span list
|
||||
this.BuildSpans(container);
|
||||
|
||||
// Build constraints
|
||||
let min_controls: number[] = [];
|
||||
let max_controls: number[] = [];
|
||||
this.BuildContainerConstraints(container, control_graph, min_controls, max_controls);
|
||||
this.BuildBufferConstraints(container, control_graph);
|
||||
this.BuildSnapConstraints(container, control_graph);
|
||||
|
||||
this.SetInitialSizeStrengths(container, control_graph, min_controls, max_controls);
|
||||
}
|
||||
|
||||
ChangeSize(new_size: number, control_graph: ControlGraph)
|
||||
{
|
||||
// Update container constraints with new size
|
||||
this.ContainerSize = new_size;
|
||||
let half_delta_size = (this.ContainerRestSize - new_size) / 2;
|
||||
let min_offset = half_delta_size + Container.SnapBorderSize;
|
||||
let max_offset = this.ContainerRestSize - min_offset;
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
constraint.Position = min_offset;
|
||||
else
|
||||
constraint.Position = max_offset;
|
||||
}
|
||||
|
||||
// Relax
|
||||
for (let i = 0; i < 50; i++)
|
||||
{
|
||||
this.ApplySizeConstraints();
|
||||
this.ApplyMinimumSizeConstraints();
|
||||
this.ApplyBufferConstraints();
|
||||
|
||||
// Do this here before non-spring constraints
|
||||
this.IntegerRoundSpans();
|
||||
|
||||
this.ApplyContainerConstraints();
|
||||
|
||||
// HERE
|
||||
this.ReevaluateSizeStrengths(control_graph);
|
||||
}
|
||||
|
||||
// TODO: Finish with a snap! Can that be made into a constraint?
|
||||
// Problem is that multiple controls may be out of line
|
||||
this.ApplySnapConstraints();
|
||||
this.ApplyContainerConstraints();
|
||||
|
||||
// Copy simulation back to the controls
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
if (this.MinSide == Side.Left)
|
||||
{
|
||||
span.Control.Position = new int2(span.Min - half_delta_size, span.Control.Position.y);
|
||||
span.Control.Size = new int2(span.Max - span.Min, span.Control.Size.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
span.Control.Position = new int2(span.Control.Position.x, span.Min - half_delta_size);
|
||||
span.Control.Size = new int2(span.Control.Size.x, span.Max - span.Min);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildSpans(container: Container)
|
||||
{
|
||||
for (let control of container.Controls)
|
||||
{
|
||||
// Anything that's not a control (e.g. a Ruler) still needs an entry in the array, even if it's empty
|
||||
if (!(control instanceof Container))
|
||||
{
|
||||
this.Spans.push(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set initial parameters
|
||||
let span = new Span();
|
||||
span.Control = control;
|
||||
if (this.MinSide == Side.Left)
|
||||
{
|
||||
span.Min = control.TopLeft.x;
|
||||
span.Max = control.BottomRight.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
span.Min = control.TopLeft.y;
|
||||
span.Max = control.BottomRight.y;
|
||||
}
|
||||
span.SizeStrength = 1;
|
||||
span.RestSizeStrength = 1;
|
||||
span.SideDistance = 10000; // Set to a high number so a single < can be used to both compare and test for validity
|
||||
this.Spans.push(span);
|
||||
|
||||
if (control instanceof Window)
|
||||
span.Title = (<Window>control).Title;
|
||||
|
||||
// Add a size constraint for each span
|
||||
let size_constraint = new SizeConstraint();
|
||||
size_constraint.Span = span;
|
||||
size_constraint.Size = span.Max - span.Min;
|
||||
this.SizeConstraints.push(size_constraint);
|
||||
}
|
||||
}
|
||||
|
||||
private ApplySizeConstraints()
|
||||
{
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
let span = constraint.Span;
|
||||
let size = span.Max - span.Min;
|
||||
let center = (span.Min + span.Max) * 0.5;
|
||||
let half_delta_size = (constraint.Size - size) * 0.5;
|
||||
let half_border_size = size * 0.5 + half_delta_size * span.SizeStrength;
|
||||
span.Min = center - half_border_size;
|
||||
span.Max = center + half_border_size;
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyMinimumSizeConstraints()
|
||||
{
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
let span = constraint.Span;
|
||||
|
||||
if (span.Max - span.Min < 20)
|
||||
{
|
||||
let center = (span.Min + span.Max) * 0.5;
|
||||
span.Min = center - 10;
|
||||
span.Max = center + 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildContainerConstraints(container: Container, control_graph: ControlGraph, min_controls: number[], max_controls: number[])
|
||||
{
|
||||
for (let i = 0; i < container.Controls.length; i++)
|
||||
{
|
||||
let min_ref_info = control_graph.RefInfos[i * 4 + this.MinSide];
|
||||
let max_ref_info = control_graph.RefInfos[i * 4 + this.MaxSide];
|
||||
|
||||
// Looking for controls that reference the external container on min/max sides
|
||||
if (min_ref_info.References(container))
|
||||
{
|
||||
let constraint = new ContainerConstraint();
|
||||
constraint.Span = this.Spans[i];
|
||||
constraint.Side = this.MinSide;
|
||||
constraint.Position = 0;
|
||||
this.ContainerConstraints.push(constraint);
|
||||
|
||||
// Track min controls for strength setting
|
||||
min_controls.push(i);
|
||||
}
|
||||
if (max_ref_info.References(container))
|
||||
{
|
||||
let constraint = new ContainerConstraint();
|
||||
constraint.Span = this.Spans[i];
|
||||
constraint.Side = this.MaxSide;
|
||||
constraint.Position = this.ContainerRestSize;
|
||||
this.ContainerConstraints.push(constraint);
|
||||
|
||||
// Track max controls for strength setting
|
||||
max_controls.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyContainerConstraints()
|
||||
{
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
constraint.Span.Min = constraint.Position;
|
||||
else
|
||||
constraint.Span.Max = constraint.Position;
|
||||
}
|
||||
}
|
||||
|
||||
private BuildBufferConstraints(container: Container, control_graph: ControlGraph)
|
||||
{
|
||||
for (let ref of control_graph.Refs)
|
||||
{
|
||||
// Only want sides on the configured axis
|
||||
if (ref.Side != this.MinSide && ref.Side != this.MaxSide)
|
||||
continue;
|
||||
|
||||
// There are two refs for each connection; ensure only one of them is used
|
||||
if (ref.FromIndex < ref.ToIndex)
|
||||
{
|
||||
let constraint = new BufferConstraint();
|
||||
constraint.Span0 = this.Spans[ref.FromIndex];
|
||||
constraint.Side = ref.Side;
|
||||
constraint.Span1 = this.Spans[ref.ToIndex];
|
||||
this.BufferConstraints.push(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyBufferConstraints()
|
||||
{
|
||||
for (let constraint of this.BufferConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
{
|
||||
let span0 = constraint.Span0;
|
||||
let span1 = constraint.Span1;
|
||||
let min = span1.Max;
|
||||
let max = span0.Min;
|
||||
let center = (min + max) * 0.5;
|
||||
let size = max - min;
|
||||
let half_delta_size = (Container.SnapBorderSize - size) * 0.5;
|
||||
let half_new_size = size * 0.5 + half_delta_size * 0.5;
|
||||
span0.Min = center + half_new_size;
|
||||
span1.Max = center - half_new_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
let span0 = constraint.Span0;
|
||||
let span1 = constraint.Span1;
|
||||
let min = span0.Max;
|
||||
let max = span1.Min;
|
||||
let center = (min + max) * 0.5;
|
||||
let size = max - min;
|
||||
let half_delta_size = (Container.SnapBorderSize - size) * 0.5;
|
||||
let half_new_size = size * 0.5 + half_delta_size * 0.5;
|
||||
span1.Min = center + half_new_size;
|
||||
span0.Max = center - half_new_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildSnapConstraints(container: Container, control_graph: ControlGraph)
|
||||
{
|
||||
for (let ref of control_graph.Refs)
|
||||
{
|
||||
if (ref.Side == this.MaxSide && ref.To != container)
|
||||
{
|
||||
let constraint = new SnapConstraint();
|
||||
constraint.MinSpan = this.Spans[ref.FromIndex];
|
||||
constraint.MaxSpan = this.Spans[ref.ToIndex];
|
||||
this.SnapConstraints.push(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IntegerRoundSpans()
|
||||
{
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
span.Min = Math.round(span.Min);
|
||||
span.Max = Math.round(span.Max);
|
||||
}
|
||||
}
|
||||
|
||||
private ApplySnapConstraints()
|
||||
{
|
||||
for (let constraint of this.SnapConstraints)
|
||||
{
|
||||
constraint.MaxSpan.Min = constraint.MinSpan.Max + Container.SnapBorderSize - 1;
|
||||
}
|
||||
|
||||
// TODO: Snap to container
|
||||
}
|
||||
|
||||
private SetInitialSizeStrengths(container: Container, control_graph: ControlGraph, min_controls: number[], max_controls: number[])
|
||||
{
|
||||
let weak_strength = 0.01;
|
||||
let strong_strength = 0.5;
|
||||
|
||||
let side_distance = 0;
|
||||
while (min_controls.length && max_controls.length)
|
||||
{
|
||||
// Mark side distances and set strong strengths before walking further
|
||||
for (let index of min_controls)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SideDistance = side_distance;
|
||||
span.SizeStrength = strong_strength;
|
||||
}
|
||||
for (let index of max_controls)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SideDistance = side_distance;
|
||||
span.SizeStrength = strong_strength;
|
||||
}
|
||||
|
||||
let next_min_controls: number[] = [];
|
||||
let next_max_controls: number[] = [];
|
||||
|
||||
// Make one graph step towards max for the min controls, setting strengths
|
||||
for (let index of min_controls)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
let ref_info = control_graph.RefInfos[index * 4 + this.MaxSide];
|
||||
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = ref_info.GetControlRef(i);
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
|
||||
// If we've hit the container this is a control that is anchored on both sides
|
||||
if (ref.To == container)
|
||||
{
|
||||
// Set it to weak so that it's always collapsable
|
||||
span.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we bump up against a span of equal distance, their anchor point is the graph's middle
|
||||
if (span.SideDistance == span_to.SideDistance)
|
||||
{
|
||||
// Mark both sides as weak to make the equally collapsable
|
||||
span.SizeStrength = weak_strength;
|
||||
span_to.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the other side has a smaller distance then this is a center control
|
||||
if (span.SideDistance > span_to.SideDistance)
|
||||
{
|
||||
// Only the control should be marked for collapse
|
||||
span.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Walk toward the max
|
||||
if (next_min_controls.indexOf(ref.ToIndex) == -1)
|
||||
next_min_controls.push(ref.ToIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Make one graph step towards min for the max controls, not setting strengths
|
||||
for (let index of max_controls)
|
||||
{
|
||||
let ref_info = control_graph.RefInfos[index * 4 + this.MinSide];
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = ref_info.GetControlRef(i);
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
|
||||
// Strengths are already set from the min controls so abort walk when coming up
|
||||
// against a min control
|
||||
if (ref.To == container || span_to.SideDistance != 10000)
|
||||
continue;
|
||||
|
||||
// Walk toward the min
|
||||
if (next_max_controls.indexOf(ref.ToIndex) == -1)
|
||||
next_max_controls.push(ref.ToIndex);
|
||||
}
|
||||
}
|
||||
|
||||
min_controls = next_min_controls;
|
||||
max_controls = next_max_controls;
|
||||
side_distance++;
|
||||
}
|
||||
|
||||
// Record initial size strength for restoration
|
||||
for (let span of this.Spans)
|
||||
span.RestSizeStrength = span.SizeStrength;
|
||||
}
|
||||
|
||||
ReevaluateSizeStrengths(control_graph: ControlGraph)
|
||||
{
|
||||
for (let index = 0; index < this.Spans.length; index++)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SizeStrength = span.RestSizeStrength;
|
||||
|
||||
let min_ref_info = control_graph.RefInfos[index * 4 + this.MinSide];
|
||||
for (let i = 0; i < min_ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = min_ref_info.GetControlRef(i);
|
||||
if (ref.ToIndex != -1)
|
||||
{
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
let size = span_to.Max - span_to.Min;
|
||||
if (size <= 20)
|
||||
{
|
||||
span.SizeStrength = 0.01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let max_ref_info = control_graph.RefInfos[index * 4 + this.MaxSide];
|
||||
for (let i = 0; i < max_ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = max_ref_info.GetControlRef(i);
|
||||
if (ref.ToIndex != -1)
|
||||
{
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
let size = span_to.Max - span_to.Min;
|
||||
if (size <= 20)
|
||||
{
|
||||
span.SizeStrength = 0.01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugLog()
|
||||
{
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
if (span)
|
||||
console.log("Span: ", span.Title, span.Min, "->", span.Max, "...", span.SideDistance, "/", span.SizeStrength);
|
||||
else
|
||||
console.log("Null Span");
|
||||
}
|
||||
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
console.log("Size Constraint: ", constraint.Span.Title, "@", constraint.Size);
|
||||
}
|
||||
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
console.log("Container Constraint: ", constraint.Span.Title, Side[constraint.Side], "@", constraint.Position);
|
||||
}
|
||||
|
||||
for (let constraint of this.BufferConstraints)
|
||||
{
|
||||
console.log("Buffer Constraint: ", constraint.Span0.Title, "->", constraint.Span1.Title, "on", Side[constraint.Side]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
export const enum SnapCode
|
||||
{
|
||||
None = 0,
|
||||
X = 1,
|
||||
Y = 2,
|
||||
}
|
||||
|
||||
function SnapControl(pos: int2, snap_pos: int2, mask: int2, p_mask: int2, n_mask: int2, top_left: int2, bottom_right: int2) : int2
|
||||
{
|
||||
let b = Container.SnapBorderSize;
|
||||
let out_mask = new int2(0);
|
||||
|
||||
// Distance from input position to opposing corners of the control
|
||||
let d_tl = int2.Abs(int2.Sub(pos, top_left));
|
||||
let d_br = int2.Abs(int2.Sub(pos, bottom_right));
|
||||
|
||||
// If any distances are within the snap border, move the snap position to them
|
||||
if (mask.x != 0)
|
||||
{
|
||||
if (d_tl.x < b)
|
||||
{
|
||||
snap_pos.x = top_left.x - p_mask.x;
|
||||
out_mask.x = -1;
|
||||
}
|
||||
if (d_br.x < b)
|
||||
{
|
||||
snap_pos.x = bottom_right.x + n_mask.x;
|
||||
out_mask.x = 1;
|
||||
}
|
||||
}
|
||||
if (mask.y != 0)
|
||||
{
|
||||
if (d_tl.y < b)
|
||||
{
|
||||
snap_pos.y = top_left.y - p_mask.y;
|
||||
out_mask.y = -1;
|
||||
}
|
||||
if (d_br.y < b)
|
||||
{
|
||||
snap_pos.y = bottom_right.y + n_mask.y;
|
||||
out_mask.y = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return out_mask;
|
||||
}
|
||||
|
||||
export function FindSnapControls(container: Container, pos: int2, mask: int2, excluding: Control[], out_controls: [Control, int2][] = null) : [SnapCode, int2]
|
||||
{
|
||||
// Selects between control edge and a border-distance outside the control edge
|
||||
let b = Container.SnapBorderSize;
|
||||
let p_mask = int2.Mul(int2.Max0(mask), new int2(b - 1));
|
||||
let n_mask = int2.Mul(int2.Min0(mask), new int2(-b + 1));
|
||||
|
||||
// Start off with no snap adjustment
|
||||
let snap_pos = pos.Copy();
|
||||
|
||||
// Snap to sibling container bounds
|
||||
let snap_code = SnapCode.None;
|
||||
for (let control of container.Controls)
|
||||
{
|
||||
if (!(control instanceof Container))
|
||||
continue;
|
||||
if (excluding.indexOf(control) != -1)
|
||||
continue;
|
||||
|
||||
var top_left = control.TopLeft;
|
||||
var bottom_right = control.BottomRight;
|
||||
|
||||
let out_mask = SnapControl(
|
||||
pos,
|
||||
snap_pos,
|
||||
mask,
|
||||
p_mask,
|
||||
n_mask,
|
||||
control.TopLeft,
|
||||
control.BottomRight);
|
||||
|
||||
snap_code |= out_mask.x != 0 ? SnapCode.X : 0;
|
||||
snap_code |= out_mask.y != 0 ? SnapCode.Y : 0;
|
||||
|
||||
// Collect sibling controls if asked to
|
||||
if (out_controls && (out_mask.x != 0 || out_mask.y != 0))
|
||||
out_controls.push([control, out_mask]);
|
||||
}
|
||||
|
||||
// Snap to parent container bounds
|
||||
let parent_size = container.ControlParentNode.Size;
|
||||
let out_mask = SnapControl(
|
||||
pos,
|
||||
snap_pos,
|
||||
mask,
|
||||
p_mask,
|
||||
n_mask,
|
||||
new int2(b),
|
||||
int2.Sub(parent_size, new int2(b)));
|
||||
|
||||
snap_code |= out_mask.x != 0 ? SnapCode.X : 0;
|
||||
snap_code |= out_mask.y != 0 ? SnapCode.Y : 0;
|
||||
|
||||
return [ snap_code, snap_pos ];
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
|
||||
interface Window
|
||||
{
|
||||
WMRootNode: DOM.Node;
|
||||
WMRootPanel: WM.Panel;
|
||||
}
|
||||
|
||||
namespace WM
|
||||
{
|
||||
export class Panel
|
||||
{
|
||||
static SnapBorderSize = 5;
|
||||
|
||||
private static TemplateHTML = "<div class='Panel'>/div>";
|
||||
|
||||
// Main generated HTML node for the control
|
||||
// TODO: Can this be made private?
|
||||
Node: DOM.Node;
|
||||
|
||||
// PART: Panel container
|
||||
Container: PanelContainer;
|
||||
|
||||
// Parent panel that is always set, even when the panel node is hidden and not part of the DOM tree
|
||||
private _ParentPanel: Panel;
|
||||
|
||||
// Rectangle coverage
|
||||
// Always ensure position/size are defined as they are required to calculate bottom-right
|
||||
private _Position = new int2(0);
|
||||
private _Size = new int2(0);
|
||||
private _BottomRight = new int2(0);
|
||||
|
||||
// Records current visibility state
|
||||
private _Visible = false;
|
||||
|
||||
constructor(position: int2, size: int2, node?: DOM.Node)
|
||||
{
|
||||
// First-time initialisation of any panel resources
|
||||
if (window.WMRootNode == null)
|
||||
{
|
||||
// TODO: Can fully flesh out root panel size and event handlers here
|
||||
window.WMRootNode = $(document.body);
|
||||
window.WMRootPanel = new Panel(new int2(0), new int2(0), window.WMRootNode);
|
||||
}
|
||||
|
||||
this.Node = node ? node : new DOM.Node(Panel.TemplateHTML);
|
||||
|
||||
// Store position/size directly so that bottom/right can be calculated
|
||||
this._Position = position;
|
||||
this._Size = size;
|
||||
this._BottomRight = int2.Add(this._Position, this._Size);
|
||||
|
||||
// Apply initial settings to the node
|
||||
this.Node.Position = this._Position;
|
||||
this.Node.Size = this._Size;
|
||||
|
||||
// Parent everything to the root to start with
|
||||
window.WMRootPanel.Container.Add(this);
|
||||
|
||||
this.Node.MouseDownEvent.Subscribe(this.OnMouseDown);
|
||||
}
|
||||
|
||||
// Cached node position
|
||||
set Position(position: int2)
|
||||
{
|
||||
this._Position = position;
|
||||
this._BottomRight = int2.Add(this._Position, this._Size);
|
||||
this.Node.Position = position;
|
||||
}
|
||||
get Position() : int2
|
||||
{
|
||||
return this._Position;
|
||||
}
|
||||
|
||||
// Cached node size
|
||||
set Size(size: int2)
|
||||
{
|
||||
this._Size = size;
|
||||
this._BottomRight = int2.Add(this._Position, this._Size);
|
||||
this.Node.Size = size;
|
||||
}
|
||||
get Size() : int2
|
||||
{
|
||||
return this._Size;
|
||||
}
|
||||
|
||||
// Alternative rectangle coverage
|
||||
set TopLeft(tl: int2)
|
||||
{
|
||||
this._Position = tl;
|
||||
this._Size = int2.Sub(this._BottomRight, tl);
|
||||
this.Node.Position = tl;
|
||||
this.Node.Size = this._Size;
|
||||
}
|
||||
get TopLeft() : int2
|
||||
{
|
||||
return this.Position;
|
||||
}
|
||||
set BottomRight(br: int2)
|
||||
{
|
||||
this.Size = int2.Sub(br, this._Position);
|
||||
}
|
||||
get BottomRight() : int2
|
||||
{
|
||||
return this._BottomRight;
|
||||
}
|
||||
|
||||
// Tells whether the panel thinks it's visible or not
|
||||
get Visible() : boolean
|
||||
{
|
||||
return this._Visible;
|
||||
}
|
||||
|
||||
// Panel this one is contained by
|
||||
set ParentPanel(parent_panel : Panel)
|
||||
{
|
||||
this._ParentPanel = parent_panel;
|
||||
}
|
||||
get ParentPanel() : Panel
|
||||
{
|
||||
return this._ParentPanel;
|
||||
}
|
||||
|
||||
// Returns the node which all added panels are parented to
|
||||
get PanelContainerNode() : DOM.Node
|
||||
{
|
||||
return this.Node;
|
||||
}
|
||||
|
||||
// Make the panel visible
|
||||
Show()
|
||||
{
|
||||
// Node will be parented to a panel but might not yet be part of the element tree
|
||||
if (this.Node.ParentElement == null)
|
||||
this.ParentPanel.Node.Append(this.Node);
|
||||
|
||||
this._Visible = true;
|
||||
}
|
||||
|
||||
// Hide the panel
|
||||
Hide()
|
||||
{
|
||||
// Safe to detach a node with no parent; saves a branch here
|
||||
this.Node.Detach();
|
||||
this._Visible = false;
|
||||
}
|
||||
|
||||
SendToTop()
|
||||
{
|
||||
let parent_panel_container = this.ParentPanel.Container;
|
||||
let parent_panels = parent_panel_container.Panels;
|
||||
|
||||
// Push to the back of the parent's panel list
|
||||
let index = parent_panels.indexOf(this);
|
||||
if (index != -1)
|
||||
{
|
||||
parent_panels.splice(index, 1);
|
||||
parent_panels.push(this);
|
||||
|
||||
// Recalculate z-indices for visible sort
|
||||
parent_panel_container.UpdateZIndices();
|
||||
}
|
||||
}
|
||||
|
||||
SendToBottom()
|
||||
{
|
||||
let parent_panel_container = this.ParentPanel.Container;
|
||||
let parent_panels = parent_panel_container.Panels;
|
||||
|
||||
// Push to the front of the parent's panel list
|
||||
let index = parent_panels.indexOf(this);
|
||||
if (index != -1)
|
||||
{
|
||||
parent_panels.splice(index, 1);
|
||||
parent_panels.unshift(this);
|
||||
|
||||
// Recalculate z-indices for visible sort
|
||||
parent_panel_container.UpdateZIndices();
|
||||
}
|
||||
}
|
||||
|
||||
private OnMouseDown = (event: MouseEvent) =>
|
||||
{
|
||||
// Allow bubble-up for this event so that it filters through nested windows
|
||||
this.SendToTop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
namespace WM
|
||||
{
|
||||
export class PanelContainer
|
||||
{
|
||||
// Panel that owns this part
|
||||
Owner: Panel;
|
||||
|
||||
// List of contained panels in z-order
|
||||
Panels: Panel[] = [];
|
||||
|
||||
constructor(owner: Panel)
|
||||
{
|
||||
this.Owner = owner;
|
||||
}
|
||||
|
||||
// Add as a child and show
|
||||
Add(panel: Panel) : Panel
|
||||
{
|
||||
// Remove from any existing parent
|
||||
// Panels always have a parent and so it can be assumed the parent has a panel container
|
||||
panel.ParentPanel.Container.Remove(panel);
|
||||
|
||||
// Parent this panel
|
||||
this.Panels.push(panel);
|
||||
panel.ParentPanel = this.Owner;
|
||||
|
||||
panel.Show();
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
// Hide and remove from this panel
|
||||
Remove(panel: Panel)
|
||||
{
|
||||
panel.Hide();
|
||||
|
||||
// Remove from the panel list and orphan
|
||||
let index = this.Panels.indexOf(panel);
|
||||
this.Panels.splice(index);
|
||||
panel.ParentPanel = window.WMRootPanel;
|
||||
}
|
||||
|
||||
UpdateZIndices()
|
||||
{
|
||||
// TODO: ZINDEX needs to be relative to parent!
|
||||
|
||||
// Set a CSS z-index for each visible panel from the bottom up
|
||||
for (let i = 0; i < this.Panels.length; i++)
|
||||
{
|
||||
let panel = this.Panels[i];
|
||||
if (!panel.Visible)
|
||||
continue;
|
||||
|
||||
// Ensure there's space between each window for the elements inside to be sorted
|
||||
// TODO: Update with full knowledge of child panels
|
||||
let z = (i + 1) * 10;
|
||||
panel.Node.ZIndex = z;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,280 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
export class PanelRef
|
||||
{
|
||||
// Primary sort key, to be combined with Side
|
||||
FromIndex: number;
|
||||
|
||||
// Cached panel reference to save needing to lookup in the parent
|
||||
From: Panel;
|
||||
|
||||
// Which side of the panel the reference is on
|
||||
Side: Side;
|
||||
|
||||
// Panel this references
|
||||
ToIndex: number;
|
||||
To: Panel;
|
||||
|
||||
|
||||
constructor(from_index: number, from: Panel, side: Side, to_index: number, to: Panel)
|
||||
{
|
||||
this.FromIndex = from_index;
|
||||
this.From = from;
|
||||
this.Side = side;
|
||||
this.ToIndex = to_index;
|
||||
this.To = to;
|
||||
}
|
||||
|
||||
get SortIndex() : number
|
||||
{
|
||||
return this.FromIndex * 4 + this.Side;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class PanelRefInfo
|
||||
{
|
||||
// Storing this allows the class to query graph properties without the extra parameter
|
||||
ParentGraph: PanelGraph;
|
||||
|
||||
// Cached panel reference to save needing to lookup in the parent
|
||||
Panel: Panel;
|
||||
|
||||
// Side specified for debug
|
||||
Side: Side;
|
||||
|
||||
// Links in the panel ref array
|
||||
StartRef: number;
|
||||
NbRefs: number;
|
||||
|
||||
constructor(parent_graph: PanelGraph, panel: Panel, side: Side)
|
||||
{
|
||||
this.ParentGraph = parent_graph;
|
||||
this.Panel = panel;
|
||||
this.Side = side;
|
||||
this.StartRef = -1;
|
||||
this.NbRefs = 0;
|
||||
}
|
||||
|
||||
References(panel: Panel) : boolean
|
||||
{
|
||||
for (let i = 0; i < this.NbRefs; i++)
|
||||
{
|
||||
if (this.ParentGraph.Refs[this.StartRef + i].To == panel)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
GetPanelRef(index: number) : PanelRef
|
||||
{
|
||||
if (index < this.NbRefs)
|
||||
return this.ParentGraph.Refs[this.StartRef + index];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
GetSide(side: Side) : PanelRefInfo
|
||||
{
|
||||
if (this.NbRefs == 0)
|
||||
return null;
|
||||
|
||||
let ref = this.ParentGraph.Refs[this.StartRef];
|
||||
return this.ParentGraph.RefInfos[ref.FromIndex * 4 + side];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class PanelGraph
|
||||
{
|
||||
Refs: PanelRef[] = [];
|
||||
|
||||
RefInfos: PanelRefInfo[] = [];
|
||||
|
||||
Build(container: PanelContainer)
|
||||
{
|
||||
// Clear existing references
|
||||
this.Refs = [ ];
|
||||
this.RefInfos = [ ];
|
||||
|
||||
// Mark all panels as unvisited
|
||||
let panel_visited: boolean[] = [];
|
||||
for (let i = 0; i < container.Panels.length; i++)
|
||||
panel_visited.push(false);
|
||||
|
||||
// Build references for each panel
|
||||
for (let i = 0; i < container.Panels.length; i++)
|
||||
{
|
||||
if (panel_visited[i])
|
||||
continue;
|
||||
|
||||
let child_panel = container.Panels[i];
|
||||
if (!child_panel.Visible)
|
||||
continue;
|
||||
|
||||
// Exempt rulers from the graph
|
||||
// TODO: Add some type traits or perhaps a property that can control this instead of just limiting to rulers
|
||||
if (child_panel instanceof Ruler)
|
||||
continue;
|
||||
|
||||
this.BuildRefs(child_panel, container, panel_visited);
|
||||
}
|
||||
|
||||
// Sort panel references, packing panels/sides next to each other
|
||||
this.Refs.sort((a: PanelRef, b: PanelRef) : number =>
|
||||
{
|
||||
return a.SortIndex - b.SortIndex;
|
||||
});
|
||||
|
||||
// Initialise the panel ref info array
|
||||
for (let i = 0; i < container.Panels.length * 4; i++)
|
||||
{
|
||||
let child_panel = container.Panels[i >> 2];
|
||||
this.RefInfos.push(new PanelRefInfo(this, child_panel, i & 3));
|
||||
}
|
||||
|
||||
// Tell each panel where its reference list starts and ends
|
||||
let last_sort_index = -1;
|
||||
for (let i = 0; i < this.Refs.length; i++)
|
||||
{
|
||||
let ref = this.Refs[i];
|
||||
let sort_index = ref.SortIndex;
|
||||
let ref_info = this.RefInfos[sort_index];
|
||||
|
||||
if (last_sort_index != sort_index)
|
||||
{
|
||||
ref_info.StartRef = i;
|
||||
last_sort_index = sort_index;
|
||||
}
|
||||
|
||||
ref_info.NbRefs++;
|
||||
}
|
||||
}
|
||||
|
||||
private BuildRefs(root_panel: Panel, container: PanelContainer, panel_visited: boolean[])
|
||||
{
|
||||
// First panel to visit is the root panel
|
||||
let to_visit_panels: Panel[] = [ root_panel ];
|
||||
|
||||
// Loop through any panels left to visit
|
||||
for (let panel_0 of to_visit_panels)
|
||||
{
|
||||
// It's possible for the same panel to be pushed onto the to-visit list more than once
|
||||
let panel_0_index = container.Panels.indexOf(panel_0);
|
||||
if (panel_visited[panel_0_index])
|
||||
continue;
|
||||
panel_visited[panel_0_index] = true;
|
||||
|
||||
let tl_0 = panel_0.TopLeft;
|
||||
let br_0 = panel_0.BottomRight;
|
||||
|
||||
// Add references to the parent panel
|
||||
let parent_panel = container.Owner;
|
||||
let b = Panel.SnapBorderSize;
|
||||
let s = parent_panel.PanelContainerNode.Size;
|
||||
if (tl_0.x <= b)
|
||||
this.Refs.push(new PanelRef(panel_0_index, panel_0, Side.Left, -1, parent_panel));
|
||||
if (tl_0.y <= b)
|
||||
this.Refs.push(new PanelRef(panel_0_index, panel_0, Side.Top, -1, parent_panel));
|
||||
if (br_0.x >= s.x - b)
|
||||
this.Refs.push(new PanelRef(panel_0_index, panel_0, Side.Right, -1, parent_panel));
|
||||
if (br_0.y >= s.y - b)
|
||||
this.Refs.push(new PanelRef(panel_0_index, panel_0, Side.Bottom, -1, parent_panel));
|
||||
|
||||
// Check candidate panels for auto-anchor intersection
|
||||
for (let panel_1 of container.Panels)
|
||||
{
|
||||
// If a panel has been previous visited, no need to add forward links as back
|
||||
// links would have been added when it was visited
|
||||
let panel_1_index = container.Panels.indexOf(panel_1);
|
||||
if (panel_visited[panel_1_index])
|
||||
continue;
|
||||
|
||||
if (!panel_1.Visible)
|
||||
continue;
|
||||
|
||||
// Exempt rulers from the graph
|
||||
// TODO: Add some type traits or perhaps a property that can control this instead of just limiting to rulers
|
||||
if (panel_1 instanceof Ruler)
|
||||
continue;
|
||||
|
||||
let tl_1 = panel_1.TopLeft;
|
||||
let br_1 = panel_1.BottomRight;
|
||||
|
||||
let side_0 = Side.None;
|
||||
let side_1 = Side.None;
|
||||
|
||||
// Check for vertical separating axis
|
||||
if (tl_1.y - br_0.y < 0 && tl_0.y - br_1.y < 0)
|
||||
{
|
||||
// Check left/right edge intersection
|
||||
if (Math.abs(tl_0.x - br_1.x) < b)
|
||||
{
|
||||
side_0 = Side.Left;
|
||||
side_1 = Side.Right;
|
||||
}
|
||||
if (Math.abs(br_0.x - tl_1.x) < b)
|
||||
{
|
||||
side_0 = Side.Right;
|
||||
side_1 = Side.Left;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for horizontal separating axis
|
||||
if (tl_1.x - br_0.x < 0 && tl_0.x - br_1.x < 0)
|
||||
{
|
||||
// Check top/bottom edge intersection
|
||||
if (Math.abs(tl_0.y - br_1.y) < b)
|
||||
{
|
||||
side_0 = Side.Top;
|
||||
side_1 = Side.Bottom;
|
||||
}
|
||||
if (Math.abs(br_0.y - tl_1.y) < b)
|
||||
{
|
||||
side_0 = Side.Bottom;
|
||||
side_1 = Side.Top;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate references for any intersection
|
||||
if (side_0 != Side.None)
|
||||
{
|
||||
this.Refs.push(new PanelRef(panel_0_index, panel_0, side_0, panel_1_index, panel_1));
|
||||
this.Refs.push(new PanelRef(panel_1_index, panel_1, side_1, panel_0_index, panel_0));
|
||||
to_visit_panels.push(panel_1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugLog()
|
||||
{
|
||||
console.log("\n--- DebugLog --------------------------------");
|
||||
|
||||
let x = Side[Side.Top];
|
||||
for (let ref_info of this.RefInfos)
|
||||
{
|
||||
if (!(ref_info.Panel instanceof Panel))
|
||||
continue;
|
||||
if (ref_info.NbRefs == 0)
|
||||
continue;
|
||||
|
||||
let names = "";
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
//let window = this.Refs[ref_info.StartRef + i].To as Window;
|
||||
//names += window.Title + ", ";
|
||||
}
|
||||
|
||||
//console.log((<Window>ref_info.Control).Title, Side[ref_info.Side] + ": ", names);
|
||||
}
|
||||
|
||||
/*for (let ref of this.Refs)
|
||||
{
|
||||
console.log((<Window>ref.From).Title, ref.Side, (<Window>ref.To).Title);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,516 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
class Span
|
||||
{
|
||||
// Only for DebugLog
|
||||
Title: string;
|
||||
|
||||
// Source panel for copying simulation back
|
||||
Panel: Panel;
|
||||
|
||||
Min: number;
|
||||
Max: number;
|
||||
|
||||
RestSizeStrength: number;
|
||||
SizeStrength: number;
|
||||
|
||||
// Number of panels between this one and the side of the container
|
||||
SideDistance: number;
|
||||
}
|
||||
|
||||
class SizeConstraint
|
||||
{
|
||||
Span: Span;
|
||||
Size: number;
|
||||
}
|
||||
|
||||
class ContainerConstraint
|
||||
{
|
||||
Span: Span;
|
||||
Side: Side;
|
||||
Position: number;
|
||||
}
|
||||
|
||||
class BufferConstraint
|
||||
{
|
||||
Span0: Span;
|
||||
Span1: Span;
|
||||
Side: Side;
|
||||
}
|
||||
|
||||
class SnapConstraint
|
||||
{
|
||||
MinSpan: Span;
|
||||
MaxSpan: Span;
|
||||
}
|
||||
|
||||
// TODO: Need to unify snapped panels into one constraint instead of multiple
|
||||
export class PanelSizer
|
||||
{
|
||||
// Allow the sizer to work independently on horizontal/vertical axes
|
||||
MinSide: Side;
|
||||
MaxSide: Side;
|
||||
|
||||
ContainerRestSize: number;
|
||||
ContainerSize: number;
|
||||
|
||||
Spans: Span[] = [];
|
||||
|
||||
ContainerConstraints: ContainerConstraint[] = [];
|
||||
BufferConstraints: BufferConstraint[] = [];
|
||||
SizeConstraints: SizeConstraint[] = [];
|
||||
SnapConstraints: SnapConstraint[] = [];
|
||||
|
||||
Clear()
|
||||
{
|
||||
this.Spans = [];
|
||||
this.ContainerConstraints = [];
|
||||
this.BufferConstraints = [];
|
||||
this.SizeConstraints = [];
|
||||
this.SnapConstraints = [];
|
||||
}
|
||||
|
||||
Build(base_side: Side, container: PanelContainer, panel_graph: PanelGraph)
|
||||
{
|
||||
this.MinSide = base_side;
|
||||
this.MaxSide = base_side + 1;
|
||||
|
||||
if (base_side == Side.Left)
|
||||
this.ContainerRestSize = container.Owner.PanelContainerNode.Size.x;
|
||||
else
|
||||
this.ContainerRestSize = container.Owner.PanelContainerNode.Size.y;
|
||||
|
||||
// Clear previous constraints
|
||||
this.Clear();
|
||||
|
||||
// Build the span list
|
||||
this.BuildSpans(container);
|
||||
|
||||
// Build constraints
|
||||
let min_panels: number[] = [];
|
||||
let max_panels: number[] = [];
|
||||
this.BuildContainerConstraints(container, panel_graph, min_panels, max_panels);
|
||||
this.BuildBufferConstraints(panel_graph);
|
||||
this.BuildSnapConstraints(container.Owner, panel_graph);
|
||||
|
||||
this.SetInitialSizeStrengths(container.Owner, panel_graph, min_panels, max_panels);
|
||||
}
|
||||
|
||||
ChangeSize(new_size: number, panel_graph: PanelGraph)
|
||||
{
|
||||
// Update container constraints with new size
|
||||
this.ContainerSize = new_size;
|
||||
let half_delta_size = (this.ContainerRestSize - new_size) / 2;
|
||||
let min_offset = half_delta_size + Panel.SnapBorderSize;
|
||||
let max_offset = this.ContainerRestSize - min_offset;
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
constraint.Position = min_offset;
|
||||
else
|
||||
constraint.Position = max_offset;
|
||||
}
|
||||
|
||||
// Relax
|
||||
for (let i = 0; i < 50; i++)
|
||||
{
|
||||
this.ApplySizeConstraints();
|
||||
this.ApplyMinimumSizeConstraints();
|
||||
this.ApplyBufferConstraints();
|
||||
|
||||
// Do this here before non-spring constraints
|
||||
this.IntegerRoundSpans();
|
||||
|
||||
this.ApplyContainerConstraints();
|
||||
|
||||
// HERE
|
||||
this.ReevaluateSizeStrengths(panel_graph);
|
||||
}
|
||||
|
||||
// TODO: Finish with a snap! Can that be made into a constraint?
|
||||
// Problem is that multiple controls may be out of line
|
||||
this.ApplySnapConstraints();
|
||||
this.ApplyContainerConstraints();
|
||||
|
||||
// Copy simulation back to the controls
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
if (this.MinSide == Side.Left)
|
||||
{
|
||||
span.Panel.Position = new int2(span.Min - half_delta_size, span.Panel.Position.y);
|
||||
span.Panel.Size = new int2(span.Max - span.Min, span.Panel.Size.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
span.Panel.Position = new int2(span.Panel.Position.x, span.Min - half_delta_size);
|
||||
span.Panel.Size = new int2(span.Panel.Size.x, span.Max - span.Min);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildSpans(container: PanelContainer)
|
||||
{
|
||||
for (let child_panel of container.Panels)
|
||||
{
|
||||
// Anything that's exempt from sizing (e.g. a Ruler) still needs an entry in the array, even if it's empty
|
||||
if (child_panel instanceof Ruler)
|
||||
{
|
||||
this.Spans.push(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set initial parameters
|
||||
let span = new Span();
|
||||
span.Panel = child_panel;
|
||||
if (this.MinSide == Side.Left)
|
||||
{
|
||||
span.Min = child_panel.TopLeft.x;
|
||||
span.Max = child_panel.BottomRight.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
span.Min = child_panel.TopLeft.y;
|
||||
span.Max = child_panel.BottomRight.y;
|
||||
}
|
||||
span.SizeStrength = 1;
|
||||
span.RestSizeStrength = 1;
|
||||
span.SideDistance = 10000; // Set to a high number so a single < can be used to both compare and test for validity
|
||||
this.Spans.push(span);
|
||||
|
||||
//if (control instanceof Window)
|
||||
// span.Title = (<Window>control).Title;
|
||||
span.Title = "NEEDS TO BE SET TO WINDOW";
|
||||
|
||||
// Add a size constraint for each span
|
||||
let size_constraint = new SizeConstraint();
|
||||
size_constraint.Span = span;
|
||||
size_constraint.Size = span.Max - span.Min;
|
||||
this.SizeConstraints.push(size_constraint);
|
||||
}
|
||||
}
|
||||
|
||||
private ApplySizeConstraints()
|
||||
{
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
let span = constraint.Span;
|
||||
let size = span.Max - span.Min;
|
||||
let center = (span.Min + span.Max) * 0.5;
|
||||
let half_delta_size = (constraint.Size - size) * 0.5;
|
||||
let half_border_size = size * 0.5 + half_delta_size * span.SizeStrength;
|
||||
span.Min = center - half_border_size;
|
||||
span.Max = center + half_border_size;
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyMinimumSizeConstraints()
|
||||
{
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
let span = constraint.Span;
|
||||
|
||||
if (span.Max - span.Min < 20)
|
||||
{
|
||||
let center = (span.Min + span.Max) * 0.5;
|
||||
span.Min = center - 10;
|
||||
span.Max = center + 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildContainerConstraints(container: PanelContainer, panel_graph: PanelGraph, min_panels: number[], max_panels: number[])
|
||||
{
|
||||
for (let i = 0; i < container.Panels.length; i++)
|
||||
{
|
||||
let min_ref_info = panel_graph.RefInfos[i * 4 + this.MinSide];
|
||||
let max_ref_info = panel_graph.RefInfos[i * 4 + this.MaxSide];
|
||||
|
||||
// Looking for panels that reference the external container on min/max sides
|
||||
if (min_ref_info.References(container.Owner))
|
||||
{
|
||||
let constraint = new ContainerConstraint();
|
||||
constraint.Span = this.Spans[i];
|
||||
constraint.Side = this.MinSide;
|
||||
constraint.Position = 0;
|
||||
this.ContainerConstraints.push(constraint);
|
||||
|
||||
// Track min panels for strength setting
|
||||
min_panels.push(i);
|
||||
}
|
||||
if (max_ref_info.References(container.Owner))
|
||||
{
|
||||
let constraint = new ContainerConstraint();
|
||||
constraint.Span = this.Spans[i];
|
||||
constraint.Side = this.MaxSide;
|
||||
constraint.Position = this.ContainerRestSize;
|
||||
this.ContainerConstraints.push(constraint);
|
||||
|
||||
// Track max panels for strength setting
|
||||
max_panels.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyContainerConstraints()
|
||||
{
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
constraint.Span.Min = constraint.Position;
|
||||
else
|
||||
constraint.Span.Max = constraint.Position;
|
||||
}
|
||||
}
|
||||
|
||||
private BuildBufferConstraints(panel_graph: PanelGraph)
|
||||
{
|
||||
for (let ref of panel_graph.Refs)
|
||||
{
|
||||
// Only want sides on the configured axis
|
||||
if (ref.Side != this.MinSide && ref.Side != this.MaxSide)
|
||||
continue;
|
||||
|
||||
// There are two refs for each connection; ensure only one of them is used
|
||||
if (ref.FromIndex < ref.ToIndex)
|
||||
{
|
||||
let constraint = new BufferConstraint();
|
||||
constraint.Span0 = this.Spans[ref.FromIndex];
|
||||
constraint.Side = ref.Side;
|
||||
constraint.Span1 = this.Spans[ref.ToIndex];
|
||||
this.BufferConstraints.push(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ApplyBufferConstraints()
|
||||
{
|
||||
for (let constraint of this.BufferConstraints)
|
||||
{
|
||||
if (constraint.Side == this.MinSide)
|
||||
{
|
||||
let span0 = constraint.Span0;
|
||||
let span1 = constraint.Span1;
|
||||
let min = span1.Max;
|
||||
let max = span0.Min;
|
||||
let center = (min + max) * 0.5;
|
||||
let size = max - min;
|
||||
let half_delta_size = (Container.SnapBorderSize - size) * 0.5;
|
||||
let half_new_size = size * 0.5 + half_delta_size * 0.5;
|
||||
span0.Min = center + half_new_size;
|
||||
span1.Max = center - half_new_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
let span0 = constraint.Span0;
|
||||
let span1 = constraint.Span1;
|
||||
let min = span0.Max;
|
||||
let max = span1.Min;
|
||||
let center = (min + max) * 0.5;
|
||||
let size = max - min;
|
||||
let half_delta_size = (Container.SnapBorderSize - size) * 0.5;
|
||||
let half_new_size = size * 0.5 + half_delta_size * 0.5;
|
||||
span1.Min = center + half_new_size;
|
||||
span0.Max = center - half_new_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BuildSnapConstraints(panel: Panel, panel_graph: PanelGraph)
|
||||
{
|
||||
for (let ref of panel_graph.Refs)
|
||||
{
|
||||
if (ref.Side == this.MaxSide && ref.To != panel)
|
||||
{
|
||||
let constraint = new SnapConstraint();
|
||||
constraint.MinSpan = this.Spans[ref.FromIndex];
|
||||
constraint.MaxSpan = this.Spans[ref.ToIndex];
|
||||
this.SnapConstraints.push(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IntegerRoundSpans()
|
||||
{
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
span.Min = Math.round(span.Min);
|
||||
span.Max = Math.round(span.Max);
|
||||
}
|
||||
}
|
||||
|
||||
private ApplySnapConstraints()
|
||||
{
|
||||
for (let constraint of this.SnapConstraints)
|
||||
{
|
||||
constraint.MaxSpan.Min = constraint.MinSpan.Max + Container.SnapBorderSize - 1;
|
||||
}
|
||||
|
||||
// TODO: Snap to container
|
||||
}
|
||||
|
||||
private SetInitialSizeStrengths(panel: Panel, panel_graph: PanelGraph, min_panels: number[], max_panels: number[])
|
||||
{
|
||||
let weak_strength = 0.01;
|
||||
let strong_strength = 0.5;
|
||||
|
||||
let side_distance = 0;
|
||||
while (min_panels.length && max_panels.length)
|
||||
{
|
||||
// Mark side distances and set strong strengths before walking further
|
||||
for (let index of min_panels)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SideDistance = side_distance;
|
||||
span.SizeStrength = strong_strength;
|
||||
}
|
||||
for (let index of max_panels)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SideDistance = side_distance;
|
||||
span.SizeStrength = strong_strength;
|
||||
}
|
||||
|
||||
let next_min_panels: number[] = [];
|
||||
let next_max_panels: number[] = [];
|
||||
|
||||
// Make one graph step towards max for the min panels, setting strengths
|
||||
for (let index of min_panels)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
let ref_info = panel_graph.RefInfos[index * 4 + this.MaxSide];
|
||||
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = ref_info.GetPanelRef(i);
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
|
||||
// If we've hit the container this is a panel that is anchored on both sides
|
||||
if (ref.To == panel)
|
||||
{
|
||||
// Set it to weak so that it's always collapsable
|
||||
span.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we bump up against a span of equal distance, their anchor point is the graph's middle
|
||||
if (span.SideDistance == span_to.SideDistance)
|
||||
{
|
||||
// Mark both sides as weak to make them equally collapsable
|
||||
span.SizeStrength = weak_strength;
|
||||
span_to.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the other side has a smaller distance then this is a center control
|
||||
if (span.SideDistance > span_to.SideDistance)
|
||||
{
|
||||
// Only the control should be marked for collapse
|
||||
span.SizeStrength = weak_strength;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Walk toward the max
|
||||
if (next_min_panels.indexOf(ref.ToIndex) == -1)
|
||||
next_min_panels.push(ref.ToIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Make one graph step towards min for the max panels, not setting strengths
|
||||
for (let index of max_panels)
|
||||
{
|
||||
let ref_info = panel_graph.RefInfos[index * 4 + this.MinSide];
|
||||
for (let i = 0; i < ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = ref_info.GetPanelRef(i);
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
|
||||
// Strengths are already set from the min panels so abort walk when coming up
|
||||
// against a min panel
|
||||
if (ref.To == panel || span_to.SideDistance != 10000)
|
||||
continue;
|
||||
|
||||
// Walk toward the min
|
||||
if (next_max_panels.indexOf(ref.ToIndex) == -1)
|
||||
next_max_panels.push(ref.ToIndex);
|
||||
}
|
||||
}
|
||||
|
||||
min_panels = next_min_panels;
|
||||
max_panels = next_max_panels;
|
||||
side_distance++;
|
||||
}
|
||||
|
||||
// Record initial size strength for restoration
|
||||
for (let span of this.Spans)
|
||||
span.RestSizeStrength = span.SizeStrength;
|
||||
}
|
||||
|
||||
ReevaluateSizeStrengths(panel_graph: PanelGraph)
|
||||
{
|
||||
for (let index = 0; index < this.Spans.length; index++)
|
||||
{
|
||||
let span = this.Spans[index];
|
||||
span.SizeStrength = span.RestSizeStrength;
|
||||
|
||||
let min_ref_info = panel_graph.RefInfos[index * 4 + this.MinSide];
|
||||
for (let i = 0; i < min_ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = min_ref_info.GetPanelRef(i);
|
||||
if (ref.ToIndex != -1)
|
||||
{
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
let size = span_to.Max - span_to.Min;
|
||||
if (size <= 20)
|
||||
{
|
||||
span.SizeStrength = 0.01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let max_ref_info = panel_graph.RefInfos[index * 4 + this.MaxSide];
|
||||
for (let i = 0; i < max_ref_info.NbRefs; i++)
|
||||
{
|
||||
let ref = max_ref_info.GetPanelRef(i);
|
||||
if (ref.ToIndex != -1)
|
||||
{
|
||||
let span_to = this.Spans[ref.ToIndex];
|
||||
let size = span_to.Max - span_to.Min;
|
||||
if (size <= 20)
|
||||
{
|
||||
span.SizeStrength = 0.01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugLog()
|
||||
{
|
||||
for (let span of this.Spans)
|
||||
{
|
||||
if (span)
|
||||
console.log("Span: ", span.Title, span.Min, "->", span.Max, "...", span.SideDistance, "/", span.SizeStrength);
|
||||
else
|
||||
console.log("Null Span");
|
||||
}
|
||||
|
||||
for (let constraint of this.SizeConstraints)
|
||||
{
|
||||
console.log("Size Constraint: ", constraint.Span.Title, "@", constraint.Size);
|
||||
}
|
||||
|
||||
for (let constraint of this.ContainerConstraints)
|
||||
{
|
||||
console.log("Container Constraint: ", constraint.Span.Title, Side[constraint.Side], "@", constraint.Position);
|
||||
}
|
||||
|
||||
for (let constraint of this.BufferConstraints)
|
||||
{
|
||||
console.log("Buffer Constraint: ", constraint.Span0.Title, "->", constraint.Span1.Title, "on", Side[constraint.Side]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
|
||||
namespace WM
|
||||
{
|
||||
export const enum RulerOrient
|
||||
{
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
export class Ruler extends Control
|
||||
{
|
||||
static TemplateHTML = "<div class='Ruler'></div>";
|
||||
|
||||
// Big enough to span entire screen while being clipped by parent
|
||||
static Size = 10000;
|
||||
|
||||
// Current orientation
|
||||
private _Orient: RulerOrient;
|
||||
|
||||
private static Position2D(orient: RulerOrient, position: number) : int2
|
||||
{
|
||||
return orient == RulerOrient.Horizontal ?
|
||||
new int2(0, position) :
|
||||
new int2(position, 0);
|
||||
}
|
||||
|
||||
private static Size2D(orient: RulerOrient) : int2
|
||||
{
|
||||
return orient == RulerOrient.Horizontal ?
|
||||
new int2(Ruler.Size, 0) :
|
||||
new int2(0, Ruler.Size);
|
||||
}
|
||||
|
||||
constructor(orient: RulerOrient, position: number)
|
||||
{
|
||||
super(new DOM.Node(Ruler.TemplateHTML),
|
||||
Ruler.Position2D(orient, position),
|
||||
Ruler.Size2D(orient));
|
||||
|
||||
this._Orient = orient;
|
||||
}
|
||||
|
||||
SetPosition(position: number) : void
|
||||
{
|
||||
this.Position = Ruler.Position2D(this._Orient, position);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
|
||||
// TODO: Hidden state, z-index, just overwrites existing data, doesn't save new layouts
|
||||
|
||||
namespace WM
|
||||
{
|
||||
class SavedControl
|
||||
{
|
||||
ID: number;
|
||||
Position: int2;
|
||||
Size: int2;
|
||||
ZIndex: number;
|
||||
}
|
||||
|
||||
class SavedContainer extends SavedControl
|
||||
{
|
||||
Controls: SavedContainer[] = [ ];
|
||||
}
|
||||
|
||||
class SavedWindow extends SavedContainer
|
||||
{
|
||||
Title: string;
|
||||
}
|
||||
|
||||
function BuildSavedContainerList(container: Container, saved_container: SavedContainer)
|
||||
{
|
||||
for (let control of container.Controls)
|
||||
{
|
||||
if (control instanceof Window)
|
||||
saved_container.Controls.push(BuildSavedWindow(control as Window));
|
||||
else if (control instanceof Container)
|
||||
saved_container.Controls.push(BuildSavedContainer(control as Container));
|
||||
}
|
||||
}
|
||||
|
||||
function BuildSavedControl(control: Control, saved_control: SavedControl) : void
|
||||
{
|
||||
saved_control.ID = control.ID;
|
||||
saved_control.Position = control.Position;
|
||||
saved_control.Size = control.Size;
|
||||
saved_control.ZIndex = control.ZIndex;
|
||||
}
|
||||
|
||||
function BuildSavedContainer(container: Container) : SavedContainer
|
||||
{
|
||||
let saved_container = new SavedContainer();
|
||||
BuildSavedControl(container, saved_container);
|
||||
BuildSavedContainerList(container, saved_container);
|
||||
return saved_container;
|
||||
}
|
||||
|
||||
function BuildSavedWindow(window: Window) : SavedWindow
|
||||
{
|
||||
let saved_window = new SavedWindow();
|
||||
BuildSavedControl(window, saved_window);
|
||||
saved_window.Title = window.Title;
|
||||
BuildSavedContainerList(window, saved_window);
|
||||
return saved_window;
|
||||
}
|
||||
|
||||
export function SaveContainer(container: Container) : string
|
||||
{
|
||||
let saved_container = BuildSavedContainer(container);
|
||||
return JSON.stringify(saved_container);
|
||||
}
|
||||
|
||||
function ApplyContainerList(container: Container, saved_container: SavedContainer)
|
||||
{
|
||||
if (saved_container.Controls === undefined)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < saved_container.Controls.length; i++)
|
||||
{
|
||||
let child_saved_control = saved_container.Controls[i];
|
||||
|
||||
// Search for control with matching ID
|
||||
for (let j = 0; j < container.Controls.length; j++)
|
||||
{
|
||||
let child_control = container.Controls[j];
|
||||
if (child_control.ID == child_saved_control.ID)
|
||||
{
|
||||
if (child_control instanceof Window)
|
||||
ApplyWindow(child_control as Window, <SavedWindow>child_saved_control);
|
||||
else if (child_control instanceof Container)
|
||||
ApplyContainer(child_control as Container, <SavedContainer>child_saved_control);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ApplyControl(control: Control, saved_control: SavedControl)
|
||||
{
|
||||
if (saved_control.Position !== undefined)
|
||||
control.Position = new int2(saved_control.Position.x, saved_control.Position.y);
|
||||
|
||||
if (saved_control.Size !== undefined)
|
||||
control.Size = new int2(saved_control.Size.x, saved_control.Size.y);
|
||||
|
||||
if (saved_control.ZIndex !== undefined && saved_control.ZIndex != null)
|
||||
control.ZIndex = saved_control.ZIndex;
|
||||
}
|
||||
|
||||
function ApplyWindow(window: Window, saved_window: SavedWindow)
|
||||
{
|
||||
ApplyControl(window, saved_window);
|
||||
|
||||
if (saved_window.Title !== undefined)
|
||||
window.Title = <string>saved_window.Title;
|
||||
|
||||
ApplyContainerList(window, saved_window);
|
||||
}
|
||||
|
||||
function ApplyContainer(container: Container, saved_container: SavedContainer)
|
||||
{
|
||||
ApplyControl(container, saved_container);
|
||||
ApplyContainerList(container, saved_container);
|
||||
}
|
||||
|
||||
export function LoadContainer(container: Container, input: string)
|
||||
{
|
||||
let saved_container = JSON.parse(input);
|
||||
ApplyContainer(container, saved_container);
|
||||
}
|
||||
}
|
|
@ -1,702 +0,0 @@
|
|||
|
||||
// TODO: If window is made embedded, remove window sizing nodes
|
||||
|
||||
namespace WM
|
||||
{
|
||||
export enum Side
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
None,
|
||||
}
|
||||
|
||||
export class Window extends Container
|
||||
{
|
||||
static TemplateHTML = `
|
||||
<div class='Window'>
|
||||
<div class='WindowTitleBar'>
|
||||
<div class='WindowTitleBarText notextsel' style='float:left'>Window Title Bar</div>
|
||||
<div class='WindowTitleBarClose notextsel' style='float:right'>O</div>
|
||||
</div>
|
||||
<div class='WindowBody'>
|
||||
<div class='WindowBodyDebug'></div>
|
||||
</div>
|
||||
<div class='WindowSizeLeft'></div>
|
||||
<div class='WindowSizeRight'></div>
|
||||
<div class='WindowSizeTop'></div>
|
||||
<div class='WindowSizeBottom'></div>
|
||||
</div>`
|
||||
|
||||
// Internal nodes
|
||||
private TitleBarNode: DOM.Node;
|
||||
private TitleBarTextNode: DOM.Node;
|
||||
private TitleBarCloseNode: DOM.Node;
|
||||
private BodyNode: DOM.Node;
|
||||
private DebugNode: DOM.Node;
|
||||
private SizeLeftNode: DOM.Node;
|
||||
private SizeRightNode: DOM.Node;
|
||||
private SizeTopNode: DOM.Node;
|
||||
private SizeBottomNode: DOM.Node;
|
||||
|
||||
// Size as specified in CSS
|
||||
private SideBarSize: number;
|
||||
|
||||
// Transient parameters for mouse move events
|
||||
private DragMouseStartPosition: int2;
|
||||
private DragWindowStartPosition: int2;
|
||||
private DragWindowStartSize: int2;
|
||||
private MouseOffset: int2;
|
||||
|
||||
private ActiveTouchID: number;
|
||||
|
||||
// List of controls that are auto-anchored to a container edge during sizing
|
||||
private AnchorControls: [Control, int2][];
|
||||
|
||||
// Transient snap rulers for each side
|
||||
private SnapRulers: Ruler[] = [ null, null, null, null ];
|
||||
|
||||
// Used to track whether a sizer is being held as opposed to moved
|
||||
private SizerMoved: boolean = false;
|
||||
|
||||
// Transient delegates for mouse size events
|
||||
private OnSizeDelegate: EventListener;
|
||||
private OnEndSizeDelegate: EventListener;
|
||||
|
||||
constructor(title: string, position: int2, size: int2)
|
||||
{
|
||||
// Create root node
|
||||
super(position, size, new DOM.Node(Window.TemplateHTML));
|
||||
|
||||
// Locate internal nodes
|
||||
this.TitleBarNode = this.Node.Find(".WindowTitleBar");
|
||||
this.TitleBarTextNode = this.Node.Find(".WindowTitleBarText");
|
||||
this.TitleBarCloseNode = this.Node.Find(".WindowTitleBarClose");
|
||||
this.BodyNode = this.Node.Find(".WindowBody");
|
||||
this.DebugNode = this.Node.Find(".WindowBodyDebug");
|
||||
this.SizeLeftNode = this.Node.Find(".WindowSizeLeft");
|
||||
this.SizeRightNode = this.Node.Find(".WindowSizeRight");
|
||||
this.SizeTopNode = this.Node.Find(".WindowSizeTop");
|
||||
this.SizeBottomNode = this.Node.Find(".WindowSizeBottom");
|
||||
|
||||
// Query CSS properties
|
||||
let body_styles = window.getComputedStyle(document.body);
|
||||
let side_bar_size = body_styles.getPropertyValue('--SideBarSize');
|
||||
this.SideBarSize = parseInt(side_bar_size);
|
||||
|
||||
// Apply the title bar text
|
||||
this.Title = title;
|
||||
|
||||
// Window move handler
|
||||
this.TitleBarNode.MouseDownEvent.Subscribe(this.OnMouseStart);
|
||||
this.TitleBarNode.TouchStartEvent.Subscribe(this.OnTouchStart);
|
||||
|
||||
// Cursor change handlers as the mouse moves over sizers
|
||||
this.SizeLeftNode.MouseMoveEvent.Subscribe(this.OnMoveOverSize);
|
||||
this.SizeRightNode.MouseMoveEvent.Subscribe(this.OnMoveOverSize);
|
||||
this.SizeTopNode.MouseMoveEvent.Subscribe(this.OnMoveOverSize);
|
||||
this.SizeBottomNode.MouseMoveEvent.Subscribe(this.OnMoveOverSize);
|
||||
|
||||
// Window sizing handlers
|
||||
this.SizeLeftNode.MouseDownEvent.Subscribe((event: MouseEvent) => { this.OnBeginSize(event, null, true); });
|
||||
this.SizeRightNode.MouseDownEvent.Subscribe((event: MouseEvent) => { this.OnBeginSize(event, null, true); });
|
||||
this.SizeTopNode.MouseDownEvent.Subscribe((event: MouseEvent) => { this.OnBeginSize(event, null, true); });
|
||||
this.SizeBottomNode.MouseDownEvent.Subscribe((event: MouseEvent) => { this.OnBeginSize(event, null, true); });
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
|
||||
|
||||
// ----- WM.Control Overrides --------------------------------------------------------
|
||||
|
||||
|
||||
/*Show() : void
|
||||
{
|
||||
super.Show();
|
||||
|
||||
// Build control graph for all controls in this container
|
||||
this.ControlGraph.Build(this);
|
||||
|
||||
// Auto-anchor to nearby controls on each show
|
||||
// This catches initial adding of controls to a new window and
|
||||
// any size changes while the window is invisible
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container)
|
||||
{
|
||||
console.log("SHOW ", this.Title);
|
||||
|
||||
let snap_tl = parent_container.GetSnapControls(this.TopLeft, new int2(-1, -1), [ this ], null, 0);
|
||||
if (snap_tl[0] != SnapCode.None)
|
||||
{
|
||||
console.log("Snapped!");
|
||||
this.Position = snap_tl[1];
|
||||
}
|
||||
|
||||
let snap_br = parent_container.GetSnapControls(this.BottomRight, new int2(1, 1), [ this ], null, 0);
|
||||
if (snap_br[0] != SnapCode.None)
|
||||
{
|
||||
console.log("Snapped!");
|
||||
this.Position = int2.Sub(snap_br[1], this.Size);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
// Uncached window title text so that any old HTML can be used
|
||||
get Title() : string
|
||||
{
|
||||
return this.TitleBarTextNode.Element.innerHTML;
|
||||
}
|
||||
set Title(title: string)
|
||||
{
|
||||
this.TitleBarTextNode.Element.innerHTML = title;
|
||||
}
|
||||
|
||||
// Add all controls to the body of the window
|
||||
get ControlParentNode() : DOM.Node
|
||||
{
|
||||
return this.BodyNode;
|
||||
}
|
||||
|
||||
set ZIndex(z_index: number)
|
||||
{
|
||||
this.Node.ZIndex = z_index;
|
||||
this.SizeLeftNode.ZIndex = z_index + 1;
|
||||
this.SizeRightNode.ZIndex = z_index + 1;
|
||||
this.SizeTopNode.ZIndex = z_index + 1;
|
||||
this.SizeBottomNode.ZIndex = z_index + 1;
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
get ZIndex() : number
|
||||
{
|
||||
return this.Node.ZIndex;
|
||||
}
|
||||
|
||||
private SetSnapRuler(side: Side, position: number)
|
||||
{
|
||||
if (this.SnapRulers[side] == null)
|
||||
{
|
||||
// Create on-demand
|
||||
let orient = (side == Side.Left || side == Side.Right) ? RulerOrient.Vertical : RulerOrient.Horizontal;
|
||||
this.SnapRulers[side] = new Ruler(orient, position);
|
||||
this.SnapRulers[side].Node.Colour = "#FFF";
|
||||
|
||||
// Add to the same parent container as the window for clipping
|
||||
if (this.ParentContainer)
|
||||
this.ParentContainer.Add(this.SnapRulers[side]);
|
||||
|
||||
// Display under all siblings
|
||||
this.SnapRulers[side].SendToBottom();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.SnapRulers[side].SetPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
private RemoveSnapRuler(side: Side)
|
||||
{
|
||||
if (this.SnapRulers[side] != null)
|
||||
{
|
||||
// Remove from the container and clear the remaining reference
|
||||
if (this.ParentContainer)
|
||||
this.ParentContainer.Remove(this.SnapRulers[side]);
|
||||
this.SnapRulers[side] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private RemoveSnapRulers()
|
||||
{
|
||||
this.RemoveSnapRuler(Side.Left);
|
||||
this.RemoveSnapRuler(Side.Right);
|
||||
this.RemoveSnapRuler(Side.Top);
|
||||
this.RemoveSnapRuler(Side.Bottom);
|
||||
}
|
||||
|
||||
private UpdateSnapRuler(side: Side, show: boolean, position: number)
|
||||
{
|
||||
if (show)
|
||||
this.SetSnapRuler(side, position);
|
||||
else
|
||||
this.RemoveSnapRuler(side);
|
||||
}
|
||||
|
||||
private UpdateTLSnapRulers(snap_code: SnapCode)
|
||||
{
|
||||
this.UpdateSnapRuler(Side.Top, (snap_code & SnapCode.Y) != 0, this.TopLeft.y - 3);
|
||||
this.UpdateSnapRuler(Side.Left, (snap_code & SnapCode.X) != 0, this.TopLeft.x - 3);
|
||||
}
|
||||
|
||||
private UpdateBRSnapRulers(snap_code: SnapCode)
|
||||
{
|
||||
this.UpdateSnapRuler(Side.Bottom, (snap_code & SnapCode.Y) != 0, this.BottomRight.y + 1);
|
||||
this.UpdateSnapRuler(Side.Right, (snap_code & SnapCode.X) != 0, this.BottomRight.x + 1);
|
||||
}
|
||||
|
||||
// --- Window movement --------------------------------------------------------------------
|
||||
|
||||
private OnBeginMove(event: Event, mouse_pos: int2)
|
||||
{
|
||||
// Prepare for drag
|
||||
this.DragMouseStartPosition = mouse_pos;
|
||||
this.DragWindowStartPosition = this.Position.Copy();
|
||||
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container)
|
||||
{
|
||||
// Display last snap configuration on initial click
|
||||
let snap_tl = FindSnapControls(parent_container, this.TopLeft, new int2(-1, -1), [ this ]);
|
||||
let snap_br = FindSnapControls(parent_container, this.BottomRight, new int2(1, 1), [ this ]);
|
||||
this.UpdateTLSnapRulers(snap_tl[0]);
|
||||
this.UpdateBRSnapRulers(snap_br[0]);
|
||||
}
|
||||
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
private OnMouseStart = (event: MouseEvent) =>
|
||||
{
|
||||
let mouse_pos = DOM.Event.GetMousePosition(event);
|
||||
this.OnBeginMove(event, mouse_pos);
|
||||
|
||||
// Dynamically add handlers for movement and release
|
||||
$(document).MouseMoveEvent.Subscribe(this.OnMouseMove);
|
||||
$(document).MouseUpEvent.Subscribe(this.OnMouseEnd);
|
||||
}
|
||||
private OnTouchStart = (event: TouchEvent) =>
|
||||
{
|
||||
// Use position of the first touch in the list
|
||||
let touch = event.changedTouches[0];
|
||||
this.ActiveTouchID = touch.identifier;
|
||||
let touch_pos = new int2(touch.pageX, touch.pageY);
|
||||
this.OnBeginMove(event, touch_pos);
|
||||
|
||||
// Dynamically add handlers for movement and release
|
||||
$(document).TouchMoveEvent.Subscribe(this.OnTouchMove);
|
||||
$(document).TouchEndEvent.Subscribe(this.OnTouchEnd);
|
||||
}
|
||||
|
||||
private OnMove(event: Event, mouse_pos: int2)
|
||||
{
|
||||
// Use the offset at the beginning of movement to drag the window around
|
||||
let offset = int2.Sub(mouse_pos, this.DragMouseStartPosition);
|
||||
this.Position = int2.Add(this.DragWindowStartPosition, offset);
|
||||
|
||||
// Snap position of the window to the edges of neighbouring windows
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container != null)
|
||||
{
|
||||
let snap_tl = FindSnapControls(parent_container, this.TopLeft, new int2(-1, -1), [ this ]);
|
||||
if (snap_tl[0] != SnapCode.None)
|
||||
this.Position = snap_tl[1];
|
||||
|
||||
let snap_br = FindSnapControls(parent_container, this.BottomRight, new int2(1, 1), [ this ]);
|
||||
if (snap_br[0] != SnapCode.None)
|
||||
this.Position = int2.Sub(snap_br[1], this.Size);
|
||||
|
||||
this.UpdateTLSnapRulers(snap_tl[0]);
|
||||
this.UpdateBRSnapRulers(snap_br[0]);
|
||||
}
|
||||
|
||||
// TODO: OnMove handler
|
||||
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
private OnMouseMove = (event: MouseEvent) =>
|
||||
{
|
||||
let mouse_pos = DOM.Event.GetMousePosition(event);
|
||||
this.OnMove(event, mouse_pos);
|
||||
}
|
||||
private OnTouchMove = (event: TouchEvent) =>
|
||||
{
|
||||
// Find the currently active touch to update movement
|
||||
for (let i = 0; i < event.changedTouches.length; i++)
|
||||
{
|
||||
let touch = event.changedTouches[i];
|
||||
if (touch.identifier == this.ActiveTouchID)
|
||||
{
|
||||
let touch_pos = new int2(touch.pageX, touch.pageY);
|
||||
this.OnMove(event, touch_pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private OnEndMove(event: Event)
|
||||
{
|
||||
this.RemoveSnapRulers();
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
private OnMouseEnd = (event: Event) =>
|
||||
{
|
||||
this.OnEndMove(event);
|
||||
|
||||
// Remove handlers added during mouse down
|
||||
$(document).MouseMoveEvent.Unsubscribe(this.OnMouseMove);
|
||||
$(document).MouseUpEvent.Unsubscribe(this.OnMouseEnd);
|
||||
}
|
||||
private OnTouchEnd = (event: Event) =>
|
||||
{
|
||||
this.OnEndMove(event);
|
||||
|
||||
// Remove handlers added during touch down
|
||||
$(document).TouchMoveEvent.Unsubscribe(this.OnTouchMove);
|
||||
$(document).TouchEndEvent.Unsubscribe(this.OnTouchEnd);
|
||||
}
|
||||
|
||||
// --- Window sizing ---------------------------------------------------------------------
|
||||
|
||||
private GetSizeMask(mouse_pos: int2) : int2
|
||||
{
|
||||
// Subtract absolute parent node position from the mouse position
|
||||
if (this.ParentNode)
|
||||
mouse_pos = int2.Sub(mouse_pos, this.ParentNode.AbsolutePosition);
|
||||
|
||||
// Use the DOM Node dimensions as they include visible borders/margins
|
||||
let offset_top_left = int2.Sub(mouse_pos, this.TopLeft);
|
||||
let offset_bottom_right = int2.Sub(this.BottomRight, mouse_pos);
|
||||
|
||||
// -1/1 for left/right top/bottom
|
||||
let mask = new int2(0);
|
||||
if (offset_bottom_right.x < this.SideBarSize && offset_bottom_right.x >= 0)
|
||||
mask.x = 1;
|
||||
if (offset_top_left.x < this.SideBarSize && offset_top_left.x >= 0)
|
||||
mask.x = -1;
|
||||
if (offset_bottom_right.y < this.SideBarSize && offset_bottom_right.y >= 0)
|
||||
mask.y = 1;
|
||||
if (offset_top_left.y < this.SideBarSize && offset_top_left.y >= 0)
|
||||
mask.y = -1;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
private SetResizeCursor(node: DOM.Node, size_mask: int2)
|
||||
{
|
||||
// Combine resize directions
|
||||
let cursor = "";
|
||||
if (size_mask.y > 0)
|
||||
cursor += "s";
|
||||
if (size_mask.y < 0)
|
||||
cursor += "n";
|
||||
if (size_mask.x > 0)
|
||||
cursor += "e";
|
||||
if (size_mask.x < 0)
|
||||
cursor += "w";
|
||||
|
||||
// Concat resize ident
|
||||
if (cursor.length > 0)
|
||||
cursor += "-resize";
|
||||
|
||||
node.Cursor = cursor;
|
||||
}
|
||||
|
||||
private RestoreCursor(node: DOM.Node)
|
||||
{
|
||||
node.Cursor = "auto";
|
||||
}
|
||||
|
||||
private OnMoveOverSize = (event: MouseEvent) =>
|
||||
{
|
||||
// Dynamically decide on the mouse cursor
|
||||
let mouse_pos = DOM.Event.GetMousePosition(event);
|
||||
let mask = this.GetSizeMask(mouse_pos);
|
||||
this.SetResizeCursor($(event.target), mask);
|
||||
}
|
||||
|
||||
private MakeControlAABB(control: Control)
|
||||
{
|
||||
// Expand control AABB by snap region to check for snap intersections
|
||||
let aabb = new AABB(control.TopLeft, control.BottomRight);
|
||||
aabb.Expand(Container.SnapBorderSize);
|
||||
return aabb;
|
||||
}
|
||||
|
||||
private TakeConnectedAnchorControls(aabb_0: AABB, anchor_controls: [Control, int2][])
|
||||
{
|
||||
// Search what's left of the anchor controls list for intersecting controls
|
||||
for (let i = 0; i < this.AnchorControls.length; )
|
||||
{
|
||||
let anchor_control = this.AnchorControls[i];
|
||||
let aabb_1 = this.MakeControlAABB(anchor_control[0]);
|
||||
|
||||
if (AABB.Intersect(aabb_0, aabb_1))
|
||||
{
|
||||
// Add to the list of connected controls
|
||||
anchor_controls.push(anchor_control);
|
||||
|
||||
// Swap the control with the back of the array and reduce array count
|
||||
// Faster than a splice for removal (unless the VM detects this)
|
||||
this.AnchorControls[i] = this.AnchorControls[this.AnchorControls.length - 1];
|
||||
this.AnchorControls.length--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only advance when there's no swap as we want to evaluate each
|
||||
// new control swapped in
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MakeAnchorControlIsland()
|
||||
{
|
||||
// TODO: Intersection test doesn't work for overlap test!
|
||||
let anchor_controls: [Control, int2][] = [ ];
|
||||
|
||||
// First find all controls connected to this one
|
||||
let aabb_0 = this.MakeControlAABB(this);
|
||||
this.TakeConnectedAnchorControls(aabb_0, anchor_controls);
|
||||
|
||||
// Then find all controls connected to each of them
|
||||
for (let anchor_control of anchor_controls)
|
||||
{
|
||||
let aabb_0 = this.MakeControlAABB(anchor_control[0]);
|
||||
this.TakeConnectedAnchorControls(aabb_0, anchor_controls);
|
||||
}
|
||||
|
||||
// Replace the anchor control list with only connected controls
|
||||
this.AnchorControls = anchor_controls;
|
||||
}
|
||||
|
||||
private GatherAnchorControls(mask: int2, gather_sibling_controls: boolean)
|
||||
{
|
||||
// Reset list just in case end event isn't received
|
||||
this.AnchorControls = [];
|
||||
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container)
|
||||
{
|
||||
// Rebuild the connectivity graph for the parent
|
||||
let control_graph = parent_container.ControlGraph;
|
||||
control_graph.Build(parent_container);
|
||||
|
||||
// Iterate all references on all sides
|
||||
let control_index = parent_container.Controls.indexOf(this);
|
||||
for (let side = 0; side < 4; side++)
|
||||
{
|
||||
let ref_info = control_graph.RefInfos[control_index * 4 + side];
|
||||
for (let ref_index = 0; ref_index < ref_info.NbRefs; ref_index++)
|
||||
{
|
||||
let ref = ref_info.GetControlRef(ref_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parent_container && gather_sibling_controls)
|
||||
{
|
||||
// Gather auto-anchor controls from siblings on side resizers only
|
||||
if ((mask.x != 0) != (mask.y != 0))
|
||||
{
|
||||
if (mask.x > 0 || mask.y > 0)
|
||||
WM.FindSnapControls(parent_container, this.BottomRight, mask, [ this ], this.AnchorControls);
|
||||
if (mask.x < 0 || mask.y < 0)
|
||||
WM.FindSnapControls(parent_container, this.TopLeft, mask, [ this ], this.AnchorControls);
|
||||
}
|
||||
|
||||
// We don't want windows at disjoint locations getting dragged into
|
||||
// the auto anchor so only allow those connected by existing snap
|
||||
// boundaries
|
||||
this.MakeAnchorControlIsland();
|
||||
}
|
||||
}
|
||||
|
||||
private OnBeginSize = (event: MouseEvent, in_mask: int2, master_control: boolean) =>
|
||||
{
|
||||
let mouse_pos = DOM.Event.GetMousePosition(event);
|
||||
|
||||
// Prepare for drag
|
||||
this.DragMouseStartPosition = mouse_pos;
|
||||
this.DragWindowStartPosition = this.Position.Copy();
|
||||
this.DragWindowStartSize = this.Size.Copy();
|
||||
|
||||
let mask = in_mask || this.GetSizeMask(mouse_pos);
|
||||
|
||||
// Start resizing gathered auto-anchors
|
||||
this.GatherAnchorControls(mask, master_control);
|
||||
for (let control of this.AnchorControls)
|
||||
{
|
||||
let window = control[0] as Window;
|
||||
if (window != null)
|
||||
window.OnBeginSize(event, control[1], false);
|
||||
}
|
||||
|
||||
// Build a control graph for the children
|
||||
// TODO: Do this always; it has to be recursive
|
||||
// TODO: Only Build
|
||||
// TODO: Move all this into Container
|
||||
this.ControlGraph.Build(this);
|
||||
this.ControlSizerX.Build(Side.Left, this, this.ControlGraph);
|
||||
this.ControlSizerY.Build(Side.Top, this, this.ControlGraph);
|
||||
|
||||
this.SizerMoved = false;
|
||||
|
||||
if (master_control)
|
||||
{
|
||||
// Display initial snap rulers
|
||||
if (mask.x > 0 || mask.y > 0)
|
||||
{
|
||||
let snap = FindSnapControls(this.ParentContainer, this.BottomRight, mask, [ this ]);
|
||||
this.UpdateBRSnapRulers(snap[0]);
|
||||
}
|
||||
if (mask.x < 0 || mask.y < 0)
|
||||
{
|
||||
let snap = FindSnapControls(this.ParentContainer, this.TopLeft, mask, [ this ]);
|
||||
this.UpdateTLSnapRulers(snap[0]);
|
||||
}
|
||||
|
||||
// If the sizer is held and not moved for a period, release all anchored controls
|
||||
// so that it can be independently moved
|
||||
setTimeout( () =>
|
||||
{
|
||||
if (this.SizerMoved == false)
|
||||
{
|
||||
this.AnchorControls = [ ];
|
||||
this.RemoveSnapRulers();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Dynamically add handlers for movement and release
|
||||
this.OnSizeDelegate = (event: MouseEvent) => { this.OnSize(event, mask, null); };
|
||||
this.OnEndSizeDelegate = (event: MouseEvent) => { this.OnEndSize(event, mask); };
|
||||
$(document).MouseMoveEvent.Subscribe(this.OnSizeDelegate);
|
||||
$(document).MouseUpEvent.Subscribe(this.OnEndSizeDelegate);
|
||||
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
}
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
|
||||
private OnSize = (event: MouseEvent, mask: int2, master_offset: int2) =>
|
||||
{
|
||||
// Use the offset from the mouse start position to drag the edge around
|
||||
let mouse_pos = DOM.Event.GetMousePosition(event);
|
||||
let offset = master_offset || int2.Sub(mouse_pos, this.DragMouseStartPosition);
|
||||
|
||||
// Chrome issues multiple redundant OnSize events even if the mouse is held still
|
||||
// Ignore those by checking for no initial mouse movement
|
||||
if (this.SizerMoved == false && offset.x == 0 && offset.y == 0)
|
||||
{
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
return;
|
||||
}
|
||||
this.SizerMoved = true;
|
||||
|
||||
// Size goes left/right with mask
|
||||
this.Size = int2.Add(this.DragWindowStartSize, int2.Mul(offset, mask));
|
||||
|
||||
// Position stays put or drifts right with mask
|
||||
let position_mask = int2.Min0(mask);
|
||||
this.Position = int2.Sub(this.DragWindowStartPosition, int2.Mul(offset, position_mask));
|
||||
|
||||
// Build up a list of controls to exclude from snapping
|
||||
// Don't snap anchor controls as they'll already be dragged around with this size event
|
||||
let exclude_controls: [Control] = [ this ];
|
||||
for (let anchor of this.AnchorControls)
|
||||
exclude_controls.push(anchor[0]);
|
||||
|
||||
// Snap edges to neighbouring edges in the parent container
|
||||
let parent_container = this.ParentContainer;
|
||||
if (parent_container != null)
|
||||
{
|
||||
if (mask.x > 0 || mask.y > 0)
|
||||
{
|
||||
let snap = FindSnapControls(parent_container, this.BottomRight, mask, exclude_controls);
|
||||
if (snap[0] != SnapCode.None)
|
||||
{
|
||||
// Adjust offset to allow anchored controls to match the snap motions
|
||||
offset = int2.Add(offset, int2.Sub(snap[1], this.BottomRight));
|
||||
|
||||
this.BottomRight = snap[1];
|
||||
}
|
||||
|
||||
// Only display ruler for master control
|
||||
if (master_offset == null)
|
||||
this.UpdateBRSnapRulers(snap[0]);
|
||||
}
|
||||
if (mask.x < 0 || mask.y < 0)
|
||||
{
|
||||
let snap = FindSnapControls(parent_container, this.TopLeft, mask, exclude_controls);
|
||||
if (snap[0] != SnapCode.None)
|
||||
{
|
||||
// Adjust offset to allow anchored controls to match the snap motions
|
||||
offset = int2.Add(offset, int2.Sub(snap[1], this.TopLeft));
|
||||
|
||||
this.TopLeft = snap[1];
|
||||
}
|
||||
|
||||
// Only display ruler for master control
|
||||
if (master_offset == null)
|
||||
this.UpdateTLSnapRulers(snap[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ControlGraph)
|
||||
{
|
||||
this.ControlSizerX.ChangeSize(this.ControlParentNode.Size.x, this.ControlGraph);
|
||||
this.ControlSizerY.ChangeSize(this.ControlParentNode.Size.y, this.ControlGraph);
|
||||
}
|
||||
|
||||
// Clamp window size to a minimum
|
||||
let min_window_size = new int2(50);
|
||||
this.Size = int2.Max(this.Size, min_window_size);
|
||||
this.Position = int2.Min(this.Position, int2.Sub(int2.Add(this.DragWindowStartPosition, this.DragWindowStartSize), min_window_size));
|
||||
|
||||
// Resize all anchored controls
|
||||
for (let control of this.AnchorControls)
|
||||
{
|
||||
let window = control[0] as Window;
|
||||
if (window != null)
|
||||
window.OnSize(event, control[1], offset);
|
||||
}
|
||||
|
||||
// The cursor will exceed the bounds of the resize element under sizing so
|
||||
// force it to whatever it needs to be here
|
||||
this.SetResizeCursor($(document.body), mask);
|
||||
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
private OnEndSize = (event: MouseEvent, mask: int2) =>
|
||||
{
|
||||
// End all anchored controls
|
||||
for (let control of this.AnchorControls)
|
||||
{
|
||||
let window = control[0] as Window;
|
||||
if (window != null)
|
||||
window.OnEndSize(event, mask);
|
||||
}
|
||||
|
||||
// Clear anchor references so they don't hang around if a window is deleted
|
||||
this.AnchorControls = [];
|
||||
|
||||
// Set cursor back to auto
|
||||
this.RestoreCursor($(document.body));
|
||||
|
||||
this.RemoveSnapRulers();
|
||||
|
||||
// Remove handlers added during mouse down
|
||||
$(document).MouseMoveEvent.Unsubscribe(this.OnSizeDelegate);
|
||||
this.OnSizeDelegate = null;
|
||||
$(document).MouseUpEvent.Unsubscribe(this.OnEndSizeDelegate);
|
||||
this.OnEndSizeDelegate = null;
|
||||
DOM.Event.StopDefaultAction(event);
|
||||
|
||||
this.UpdateDebugText();
|
||||
}
|
||||
|
||||
private UpdateDebugText()
|
||||
{
|
||||
let text = "";
|
||||
text += this.BottomRight.x;
|
||||
this.DebugNode.SetText(text);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
//
|
||||
// How I configured this project:
|
||||
//
|
||||
// 1. Download and install TypeScript for Visual Studio 2015 (https://www.microsoft.com/en-us/download/details.aspx?id=48593)
|
||||
// 2. Setup "typescript.tsdk" to point to "C:\\Program Files (x86)\\Microsoft SDKs\\TypeScript\\2.0\\"
|
||||
// 3. Copy https://raw.githubusercontent.com/Microsoft/TypeScript/release-2.0/lib/tsserver.js to the TypeScript directory.
|
||||
//
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "amd",
|
||||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"outFile": "remotery.js",
|
||||
"sourceMap": true
|
||||
},
|
||||
"files": [
|
||||
"Math.ts",
|
||||
"Hash.ts",
|
||||
"DOM.ts",
|
||||
"DOM_Node.ts",
|
||||
"WM_Panel.ts",
|
||||
"WM_PanelContainer.ts",
|
||||
"WM_PanelGraph.ts",
|
||||
"WM_PanelSizer.ts",
|
||||
"WM_Control.ts",
|
||||
"WM_Ruler.ts",
|
||||
"WM_Container.ts",
|
||||
"WM_ControlGraph.ts",
|
||||
"WM_ControlSizer.ts",
|
||||
"WM_ControlSnapper.ts",
|
||||
"WM_Window.ts",
|
||||
"WM_Serialisation.ts",
|
||||
"Test.ts"
|
||||
]
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
|
||||
Console = (function()
|
||||
{
|
||||
var BORDER = 10;
|
||||
var HEIGHT = 200;
|
||||
|
||||
|
||||
function Console(wm, server)
|
||||
{
|
||||
// Create the window and its controls
|
||||
this.Window = wm.AddWindow("Console", 10, 10, 100, 100);
|
||||
this.PageContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160));
|
||||
DOM.Node.AddClass(this.PageContainer.Node, "ConsoleText");
|
||||
this.AppContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160));
|
||||
DOM.Node.AddClass(this.AppContainer.Node, "ConsoleText");
|
||||
this.UserInput = this.Window.AddControlNew(new WM.EditBox(10, 5, 400, 30, "Input", ""));
|
||||
this.UserInput.SetChangeHandler(Bind(ProcessInput, this));
|
||||
this.Window.ShowNoAnim();
|
||||
|
||||
// This accumulates log text as fast as is required
|
||||
this.PageTextBuffer = "";
|
||||
this.PageTextUpdatePending = false;
|
||||
this.AppTextBuffer = "";
|
||||
this.AppTextUpdatePending = false;
|
||||
|
||||
// Setup command history control
|
||||
this.CommandHistory = LocalStore.Get("App", "Global", "CommandHistory", [ ]);
|
||||
this.CommandIndex = 0;
|
||||
this.MaxNbCommands = 200;
|
||||
DOM.Event.AddHandler(this.UserInput.EditNode, "keydown", Bind(OnKeyPress, this));
|
||||
DOM.Event.AddHandler(this.UserInput.EditNode, "focus", Bind(OnFocus, this));
|
||||
|
||||
// At a much lower frequency this will update the console window
|
||||
window.setInterval(Bind(UpdateHTML, this), 500);
|
||||
|
||||
// Setup log requests from the server
|
||||
this.Server = server;
|
||||
server.SetConsole(this);
|
||||
server.AddMessageHandler("LOGM", Bind(OnLog, this));
|
||||
|
||||
this.Window.SetOnResize(Bind(OnUserResize, this));
|
||||
}
|
||||
|
||||
|
||||
Console.prototype.Log = function(text)
|
||||
{
|
||||
this.PageTextBuffer = LogText(this.PageTextBuffer, text);
|
||||
this.PageTextUpdatePending = true;
|
||||
}
|
||||
|
||||
|
||||
Console.prototype.WindowResized = function(width, height)
|
||||
{
|
||||
// Place window
|
||||
this.Window.SetPosition(BORDER, height - BORDER - 200);
|
||||
this.Window.SetSize(width - 2 * BORDER, HEIGHT);
|
||||
|
||||
ResizeInternals(this);
|
||||
}
|
||||
|
||||
|
||||
function OnLog(self, socket, data_view)
|
||||
{
|
||||
var data_view_reader = new DataViewReader(data_view, 4);
|
||||
var text = data_view_reader.GetString();
|
||||
self.AppTextBuffer = LogText(self.AppTextBuffer, text);
|
||||
self.AppTextUpdatePending = true;
|
||||
}
|
||||
|
||||
|
||||
function LogText(existing_text, new_text)
|
||||
{
|
||||
// Filter the text a little to make it safer
|
||||
if (new_text == null)
|
||||
new_text = "NULL";
|
||||
|
||||
// Find and convert any HTML entities, ensuring the browser doesn't parse any embedded HTML code
|
||||
// This also allows the log to contain arbitrary C++ code (e.g. assert comparison operators)
|
||||
new_text = Convert.string_to_html_entities(new_text);
|
||||
|
||||
// Prefix date and end with new line
|
||||
var d = new Date();
|
||||
new_text = "[" + d.toLocaleTimeString() + "] " + new_text + "<br>";
|
||||
|
||||
// Append to local text buffer and ensure clip the oldest text to ensure a max size
|
||||
existing_text = existing_text + new_text;
|
||||
var max_len = 10 * 1024;
|
||||
var len = existing_text.length;
|
||||
if (len > max_len)
|
||||
existing_text = existing_text.substr(len - max_len, max_len);
|
||||
|
||||
return existing_text;
|
||||
}
|
||||
|
||||
function OnUserResize(self, evt)
|
||||
{
|
||||
ResizeInternals(self);
|
||||
}
|
||||
|
||||
function ResizeInternals(self)
|
||||
{
|
||||
// Place controls
|
||||
var parent_size = self.Window.Size;
|
||||
var mid_w = parent_size[0] / 3;
|
||||
self.UserInput.SetPosition(BORDER, parent_size[1] - 2 * BORDER - 30);
|
||||
self.UserInput.SetSize(parent_size[0] - 100, 18);
|
||||
var output_height = self.UserInput.Position[1] - 2 * BORDER;
|
||||
self.PageContainer.SetPosition(BORDER, BORDER);
|
||||
self.PageContainer.SetSize(mid_w - 2 * BORDER, output_height);
|
||||
self.AppContainer.SetPosition(mid_w, BORDER);
|
||||
self.AppContainer.SetSize(parent_size[0] - mid_w - BORDER, output_height);
|
||||
}
|
||||
|
||||
|
||||
function UpdateHTML(self)
|
||||
{
|
||||
// Reset the current text buffer as html
|
||||
|
||||
if (self.PageTextUpdatePending)
|
||||
{
|
||||
var page_node = self.PageContainer.Node;
|
||||
page_node.innerHTML = self.PageTextBuffer;
|
||||
page_node.scrollTop = page_node.scrollHeight;
|
||||
self.PageTextUpdatePending = false;
|
||||
}
|
||||
|
||||
if (self.AppTextUpdatePending)
|
||||
{
|
||||
var app_node = self.AppContainer.Node;
|
||||
app_node.innerHTML = self.AppTextBuffer;
|
||||
app_node.scrollTop = app_node.scrollHeight;
|
||||
self.AppTextUpdatePending = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ProcessInput(self, node)
|
||||
{
|
||||
// Send the message exactly
|
||||
var msg = node.value;
|
||||
self.Server.Send("CONI" + msg);
|
||||
|
||||
// Emit to console and clear
|
||||
self.Log("> " + msg);
|
||||
self.UserInput.SetValue("");
|
||||
|
||||
// Keep track of recently issued commands, with an upper bound
|
||||
self.CommandHistory.push(msg);
|
||||
var extra_commands = self.CommandHistory.length - self.MaxNbCommands;
|
||||
if (extra_commands > 0)
|
||||
self.CommandHistory.splice(0, extra_commands);
|
||||
|
||||
// Set command history index to the most recent command
|
||||
self.CommandIndex = self.CommandHistory.length;
|
||||
|
||||
// Backup to local store
|
||||
LocalStore.Set("App", "Global", "CommandHistory", self.CommandHistory);
|
||||
|
||||
// Keep focus with the edit box
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function OnKeyPress(self, evt)
|
||||
{
|
||||
evt = DOM.Event.Get(evt);
|
||||
|
||||
if (evt.keyCode == Keyboard.Codes.UP)
|
||||
{
|
||||
if (self.CommandHistory.length > 0)
|
||||
{
|
||||
// Cycle backwards through the command history
|
||||
self.CommandIndex--;
|
||||
if (self.CommandIndex < 0)
|
||||
self.CommandIndex = self.CommandHistory.length - 1;
|
||||
var command = self.CommandHistory[self.CommandIndex];
|
||||
self.UserInput.SetValue(command);
|
||||
}
|
||||
|
||||
// Stops default behaviour of moving cursor to the beginning
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
|
||||
else if (evt.keyCode == Keyboard.Codes.DOWN)
|
||||
{
|
||||
if (self.CommandHistory.length > 0)
|
||||
{
|
||||
// Cycle fowards through the command history
|
||||
self.CommandIndex = (self.CommandIndex + 1) % self.CommandHistory.length;
|
||||
var command = self.CommandHistory[self.CommandIndex];
|
||||
self.UserInput.SetValue(command);
|
||||
}
|
||||
|
||||
// Stops default behaviour of moving cursor to the end
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnFocus(self)
|
||||
{
|
||||
// Reset command index on focus
|
||||
self.CommandIndex = self.CommandHistory.length;
|
||||
}
|
||||
|
||||
|
||||
return Console;
|
||||
})();
|
|
@ -1,47 +0,0 @@
|
|||
|
||||
//
|
||||
// Simple wrapper around DataView that auto-advances the read offset and provides
|
||||
// a few common data type conversions specific to this app
|
||||
//
|
||||
DataViewReader = (function ()
|
||||
{
|
||||
function DataViewReader(data_view, offset)
|
||||
{
|
||||
this.DataView = data_view;
|
||||
this.Offset = offset;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetUInt32 = function ()
|
||||
{
|
||||
var v = this.DataView.getUint32(this.Offset, true);
|
||||
this.Offset += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetUInt64 = function ()
|
||||
{
|
||||
var v = this.DataView.getFloat64(this.Offset, true);
|
||||
this.Offset += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetStringOfLength = function (string_length)
|
||||
{
|
||||
var string = "";
|
||||
for (var i = 0; i < string_length; i++)
|
||||
{
|
||||
string += String.fromCharCode(this.DataView.getInt8(this.Offset));
|
||||
this.Offset++;
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
DataViewReader.prototype.GetString = function ()
|
||||
{
|
||||
var string_length = this.GetUInt32();
|
||||
return this.GetStringOfLength(string_length);
|
||||
}
|
||||
|
||||
return DataViewReader;
|
||||
})();
|
|
@ -1,61 +0,0 @@
|
|||
|
||||
|
||||
PixelTimeRange = (function()
|
||||
{
|
||||
function PixelTimeRange(start_us, span_us, span_px)
|
||||
{
|
||||
this.Span_px = span_px;
|
||||
this.Set(start_us, span_us);
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.Set = function(start_us, span_us)
|
||||
{
|
||||
this.Start_us = start_us;
|
||||
this.Span_us = span_us;
|
||||
this.End_us = this.Start_us + span_us;
|
||||
this.usPerPixel = this.Span_px / this.Span_us;
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.SetStart = function(start_us)
|
||||
{
|
||||
this.Start_us = start_us;
|
||||
this.End_us = start_us + this.Span_us;
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.SetEnd = function(end_us)
|
||||
{
|
||||
this.End_us = end_us;
|
||||
this.Start_us = end_us - this.Span_us;
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.SetPixelSpan = function(span_px)
|
||||
{
|
||||
this.Span_px = span_px;
|
||||
this.usPerPixel = this.Span_px / this.Span_us;
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.PixelOffset = function(time_us)
|
||||
{
|
||||
return Math.floor((time_us - this.Start_us) * this.usPerPixel);
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.PixelSize = function(time_us)
|
||||
{
|
||||
return Math.floor(time_us * this.usPerPixel);
|
||||
}
|
||||
|
||||
|
||||
PixelTimeRange.prototype.Clone = function()
|
||||
{
|
||||
return new PixelTimeRange(this.Start_us, this.Span_us, this.Span_px);
|
||||
}
|
||||
|
||||
|
||||
return PixelTimeRange;
|
||||
})();
|
|
@ -1,338 +0,0 @@
|
|||
|
||||
//
|
||||
// TODO: Window resizing needs finer-grain control
|
||||
// TODO: Take into account where user has moved the windows
|
||||
// TODO: Controls need automatic resizing within their parent windows
|
||||
//
|
||||
|
||||
|
||||
Settings = (function()
|
||||
{
|
||||
function Settings()
|
||||
{
|
||||
this.IsPaused = false;
|
||||
}
|
||||
|
||||
return Settings;
|
||||
|
||||
})();
|
||||
|
||||
|
||||
Remotery = (function()
|
||||
{
|
||||
// crack the url and get the parameter we want
|
||||
var getUrlParameter = function getUrlParameter( search_param)
|
||||
{
|
||||
var page_url = decodeURIComponent( window.location.search.substring(1) ),
|
||||
url_vars = page_url.split('&'),
|
||||
param_name,
|
||||
i;
|
||||
|
||||
for (i = 0; i < url_vars.length; i++)
|
||||
{
|
||||
param_name = url_vars[i].split('=');
|
||||
|
||||
if (param_name[0] === search_param)
|
||||
{
|
||||
return param_name[1] === undefined ? true : param_name[1];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function Remotery()
|
||||
{
|
||||
this.WindowManager = new WM.WindowManager();
|
||||
this.Settings = new Settings();
|
||||
|
||||
// "addr" param is ip:port and will override the local store version if passed in the URL
|
||||
var addr = getUrlParameter( "addr" );
|
||||
if ( addr != null )
|
||||
this.ConnectionAddress = "ws://" + addr + "/rmt";
|
||||
else
|
||||
this.ConnectionAddress = LocalStore.Get("App", "Global", "ConnectionAddress", "ws://127.0.0.1:17815/rmt");
|
||||
|
||||
this.Server = new WebSocketConnection();
|
||||
this.Server.AddConnectHandler(Bind(OnConnect, this));
|
||||
|
||||
// Create the console up front as everything reports to it
|
||||
this.Console = new Console(this.WindowManager, this.Server);
|
||||
|
||||
// Create required windows
|
||||
this.TitleWindow = new TitleWindow(this.WindowManager, this.Settings, this.Server, this.ConnectionAddress);
|
||||
this.TitleWindow.SetConnectionAddressChanged(Bind(OnAddressChanged, this));
|
||||
this.TimelineWindow = new TimelineWindow(this.WindowManager, this.Settings, this.Server, Bind(OnTimelineCheck, this));
|
||||
this.TimelineWindow.SetOnHover(Bind(OnSampleHover, this));
|
||||
this.TimelineWindow.SetOnSelected(Bind(OnSampleSelected, this));
|
||||
|
||||
this.NbSampleWindows = 0;
|
||||
this.SampleWindows = { };
|
||||
this.FrameHistory = { };
|
||||
this.SelectedFrames = { };
|
||||
this.NameMap = { };
|
||||
|
||||
this.Server.AddMessageHandler("SMPL", Bind(OnSamples, this));
|
||||
this.Server.AddMessageHandler("SSMP", Bind(OnSampleName, this));
|
||||
|
||||
// Kick-off the auto-connect loop
|
||||
AutoConnect(this);
|
||||
|
||||
// Hook up resize event handler
|
||||
DOM.Event.AddHandler(window, "resize", Bind(OnResizeWindow, this));
|
||||
OnResizeWindow(this);
|
||||
|
||||
// Hook up browser-native canvas refresh
|
||||
this.DisplayFrame = 0;
|
||||
this.LastKnownPause = this.Settings.IsPaused;
|
||||
var self = this;
|
||||
(function display_loop()
|
||||
{
|
||||
window.requestAnimationFrame(display_loop);
|
||||
DrawTimeline(self);
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
function AutoConnect(self)
|
||||
{
|
||||
// Only attempt to connect if there isn't already a connection or an attempt to connect
|
||||
if (!self.Server.Connected())
|
||||
self.Server.Connect(self.ConnectionAddress);
|
||||
|
||||
// Always schedule another check
|
||||
window.setTimeout(Bind(AutoConnect, self), 2000);
|
||||
}
|
||||
|
||||
|
||||
function OnConnect(self)
|
||||
{
|
||||
// Connection address has been validated
|
||||
LocalStore.Set("App", "Global", "ConnectionAddress", self.ConnectionAddress);
|
||||
}
|
||||
|
||||
|
||||
function OnAddressChanged(self, node)
|
||||
{
|
||||
// Update and disconnect, relying on auto-connect to reconnect
|
||||
self.ConnectionAddress = node.value;
|
||||
self.Server.Disconnect();
|
||||
|
||||
// Give input focus away
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function DrawTimeline(self)
|
||||
{
|
||||
// Has pause state changed?
|
||||
if (self.Settings.IsPaused != self.LastKnownPaused)
|
||||
{
|
||||
// When switching TO paused, draw one last frame to ensure the sample text gets drawn
|
||||
self.LastKnownPaused = self.Settings.IsPaused;
|
||||
self.TimelineWindow.DrawAllRows();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't waste time drawing the timeline when paused
|
||||
if (self.Settings.IsPaused)
|
||||
return;
|
||||
|
||||
// requestAnimationFrame can run up to 60hz which is way too much for drawing the timeline
|
||||
// Assume it's running at 60hz and skip frames to achieve 10hz instead
|
||||
// Doing this instead of using setTimeout because it's better for browser rendering (or; will be once WebGL is in use)
|
||||
// TODO: Expose as config variable because high refresh rate is great when using a separate viewiing machine
|
||||
if ((self.DisplayFrame % 10) == 0)
|
||||
self.TimelineWindow.DrawAllRows();
|
||||
|
||||
self.DisplayFrame++;
|
||||
}
|
||||
|
||||
|
||||
function DecodeSample(self, data_view_reader)
|
||||
{
|
||||
var sample = {};
|
||||
|
||||
// Get name hash and lookup name it map
|
||||
sample.name_hash = data_view_reader.GetUInt32();
|
||||
sample.name = self.NameMap[sample.name_hash];
|
||||
|
||||
// If the name doesn't exist in the map yet, request it from the server
|
||||
if (sample.name == undefined)
|
||||
{
|
||||
// Meanwhile, store the hash as the name
|
||||
sample.name = { "string": sample.name_hash };
|
||||
self.NameMap[sample.name_hash] = sample.name;
|
||||
self.Server.Send("GSMP" + sample.name_hash);
|
||||
}
|
||||
|
||||
// Get the rest of the sample data
|
||||
sample.id = data_view_reader.GetUInt32();
|
||||
sample.colour = data_view_reader.GetStringOfLength(7);
|
||||
sample.us_start = data_view_reader.GetUInt64();
|
||||
sample.us_length = data_view_reader.GetUInt64();
|
||||
sample.us_self = data_view_reader.GetUInt64();
|
||||
sample.call_count = data_view_reader.GetUInt32();
|
||||
sample.recurse_depth = data_view_reader.GetUInt32();
|
||||
|
||||
// Calculate dependent properties
|
||||
sample.ms_length = (sample.us_length / 1000.0).toFixed(3);
|
||||
sample.ms_self = (sample.us_self / 1000.0).toFixed(3);
|
||||
|
||||
// Recurse into children
|
||||
sample.children = [];
|
||||
DecodeSampleArray(self, data_view_reader, sample.children);
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
|
||||
function DecodeSampleArray(self, data_view_reader, samples)
|
||||
{
|
||||
var nb_samples = data_view_reader.GetUInt32();
|
||||
for (var i = 0; i < nb_samples; i++)
|
||||
{
|
||||
var sample = DecodeSample(self, data_view_reader);
|
||||
samples.push(sample)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function DecodeSamples(self, data_view_reader)
|
||||
{
|
||||
// Message-specific header
|
||||
var message = { };
|
||||
message.thread_name = data_view_reader.GetString();
|
||||
message.nb_samples = data_view_reader.GetUInt32();
|
||||
message.sample_digest = data_view_reader.GetUInt32();
|
||||
|
||||
// Read samples
|
||||
message.samples = [];
|
||||
message.samples.push(DecodeSample(self, data_view_reader));
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
function OnSamples(self, socket, data_view)
|
||||
{
|
||||
// Discard any new samples while paused
|
||||
if (self.Settings.IsPaused)
|
||||
return;
|
||||
|
||||
// Binary decode incoming sample data
|
||||
var message = DecodeSamples(self, new DataViewReader(data_view, 8));
|
||||
var name = message.thread_name;
|
||||
|
||||
// Add to frame history for this thread
|
||||
var thread_frame = new ThreadFrame(message);
|
||||
if (!(name in self.FrameHistory))
|
||||
self.FrameHistory[name] = [ ];
|
||||
var frame_history = self.FrameHistory[name];
|
||||
frame_history.push(thread_frame);
|
||||
|
||||
// Discard old frames to keep memory-use constant
|
||||
var max_nb_frames = 10000;
|
||||
var extra_frames = frame_history.length - max_nb_frames;
|
||||
if (extra_frames > 0)
|
||||
frame_history.splice(0, extra_frames);
|
||||
|
||||
// Create sample windows on-demand
|
||||
if (!(name in self.SampleWindows))
|
||||
{
|
||||
self.SampleWindows[name] = new SampleWindow(self.WindowManager, name, self.NbSampleWindows);
|
||||
self.SampleWindows[name].WindowResized(self.TimelineWindow.Window, self.Console.Window);
|
||||
self.NbSampleWindows++;
|
||||
MoveSampleWindows(this);
|
||||
}
|
||||
|
||||
// Set on the window and timeline
|
||||
self.SampleWindows[name].OnSamples(message.nb_samples, message.sample_digest, message.samples);
|
||||
self.TimelineWindow.OnSamples(name, frame_history);
|
||||
}
|
||||
|
||||
|
||||
function OnSampleName(self, socket, data_view)
|
||||
{
|
||||
// Add any names sent by the server to the local map
|
||||
var data_view_reader = new DataViewReader(data_view, 4);
|
||||
var name_hash = data_view_reader.GetUInt32();
|
||||
var name = data_view_reader.GetString();
|
||||
self.NameMap[name_hash].string = name;
|
||||
}
|
||||
|
||||
|
||||
function OnTimelineCheck(self, name, evt)
|
||||
{
|
||||
// Show/hide the equivalent sample window and move all the others to occupy any left-over space
|
||||
var target = DOM.Event.GetNode(evt);
|
||||
self.SampleWindows[name].SetVisible(target.checked);
|
||||
MoveSampleWindows(self);
|
||||
}
|
||||
|
||||
|
||||
function MoveSampleWindows(self)
|
||||
{
|
||||
// Stack all windows next to each other
|
||||
var xpos = 0;
|
||||
for (var i in self.SampleWindows)
|
||||
{
|
||||
var sample_window = self.SampleWindows[i];
|
||||
if (sample_window.Visible)
|
||||
sample_window.SetXPos(xpos++, self.TimelineWindow.Window, self.Console.Window);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnSampleHover(self, thread_name, hover)
|
||||
{
|
||||
// Hover only changes sample window contents when paused
|
||||
var sample_window = self.SampleWindows[thread_name];
|
||||
if (sample_window && self.Settings.IsPaused)
|
||||
{
|
||||
if (hover == null)
|
||||
{
|
||||
// When there's no hover, go back to the selected frame
|
||||
if (self.SelectedFrames[thread_name])
|
||||
{
|
||||
var frame = self.SelectedFrames[thread_name];
|
||||
sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Populate with sample under hover
|
||||
var frame = hover[0];
|
||||
sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnSampleSelected(self, thread_name, select)
|
||||
{
|
||||
// Lookup sample window set the frame samples on it
|
||||
if (select && thread_name in self.SampleWindows)
|
||||
{
|
||||
var sample_window = self.SampleWindows[thread_name];
|
||||
var frame = select[0];
|
||||
self.SelectedFrames[thread_name] = frame;
|
||||
sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnResizeWindow(self)
|
||||
{
|
||||
// Resize windows
|
||||
var w = window.innerWidth;
|
||||
var h = window.innerHeight;
|
||||
self.Console.WindowResized(w, h);
|
||||
self.TitleWindow.WindowResized(w, h);
|
||||
self.TimelineWindow.WindowResized(w, h, self.TitleWindow.Window);
|
||||
for (var i in self.SampleWindows)
|
||||
self.SampleWindows[i].WindowResized(self.TimelineWindow.Window, self.Console.Window);
|
||||
}
|
||||
|
||||
|
||||
return Remotery;
|
||||
})();
|
|
@ -1,215 +0,0 @@
|
|||
|
||||
SampleWindow = (function()
|
||||
{
|
||||
function SampleWindow(wm, name, offset)
|
||||
{
|
||||
// Sample digest for checking if grid needs to be repopulated
|
||||
this.NbSamples = 0;
|
||||
this.SampleDigest = null;
|
||||
|
||||
// Source sample reference to reduce repopulation
|
||||
this.Samples = null;
|
||||
|
||||
this.XPos = 10 + offset * 410;
|
||||
this.Window = wm.AddWindow(name, 100, 100, 100, 100);
|
||||
this.Window.ShowNoAnim();
|
||||
this.Visible = true;
|
||||
|
||||
// Create a grid that's indexed by the unique sample ID
|
||||
this.Grid = this.Window.AddControlNew(new WM.Grid());
|
||||
var cell_data =
|
||||
{
|
||||
Name: "Samples",
|
||||
Length: "Time (ms)",
|
||||
Self: "Self (ms)",
|
||||
Calls: "Calls",
|
||||
Recurse: "Recurse",
|
||||
};
|
||||
var cell_classes =
|
||||
{
|
||||
Name: "SampleTitleNameCell",
|
||||
Length: "SampleTitleTimeCell",
|
||||
Self: "SampleTitleTimeCell",
|
||||
Calls: "SampleTitleCountCell",
|
||||
Recurse: "SampleTitleCountCell",
|
||||
};
|
||||
this.RootRow = this.Grid.Rows.Add(cell_data, "GridGroup", cell_classes);
|
||||
this.RootRow.Rows.AddIndex("_ID");
|
||||
}
|
||||
|
||||
|
||||
SampleWindow.prototype.SetXPos = function(xpos, top_window, bottom_window)
|
||||
{
|
||||
Anim.Animate(
|
||||
Bind(AnimatedMove, this, top_window, bottom_window),
|
||||
this.XPos, 10 + xpos * 410, 0.25);
|
||||
}
|
||||
|
||||
|
||||
function AnimatedMove(self, top_window, bottom_window, val)
|
||||
{
|
||||
self.XPos = val;
|
||||
self.WindowResized(top_window, bottom_window);
|
||||
}
|
||||
|
||||
|
||||
SampleWindow.prototype.SetVisible = function(visible)
|
||||
{
|
||||
if (visible != this.Visible)
|
||||
{
|
||||
if (visible == true)
|
||||
this.Window.Show();
|
||||
else
|
||||
this.Window.Hide();
|
||||
|
||||
this.Visible = visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SampleWindow.prototype.WindowResized = function(top_window, bottom_window)
|
||||
{
|
||||
var top = top_window.Position[1] + top_window.Size[1] + 10;
|
||||
this.Window.SetPosition(this.XPos, top_window.Position[1] + top_window.Size[1] + 10);
|
||||
this.Window.SetSize(400, bottom_window.Position[1] - 10 - top);
|
||||
}
|
||||
|
||||
|
||||
SampleWindow.prototype.OnSamples = function(nb_samples, sample_digest, samples)
|
||||
{
|
||||
if (!this.Visible)
|
||||
return;
|
||||
|
||||
// If the source hasn't changed, don't repopulate
|
||||
if (this.Samples == samples)
|
||||
return;
|
||||
this.Samples = samples;
|
||||
|
||||
// Recreate all the HTML if the number of samples gets bigger
|
||||
if (nb_samples > this.NbSamples)
|
||||
{
|
||||
GrowGrid(this.RootRow, nb_samples);
|
||||
this.NbSamples = nb_samples;
|
||||
}
|
||||
|
||||
// If the content of the samples changes from previous update, update them all
|
||||
if (this.SampleDigest != sample_digest)
|
||||
{
|
||||
this.RootRow.Rows.ClearIndex("_ID");
|
||||
var index = UpdateAllSampleFields(this.RootRow, samples, 0, "");
|
||||
this.SampleDigest = sample_digest;
|
||||
|
||||
// Clear out any left-over rows
|
||||
for (var i = index; i < this.RootRow.Rows.Rows.length; i++)
|
||||
{
|
||||
var row = this.RootRow.Rows.Rows[i];
|
||||
DOM.Node.Hide(row.Node);
|
||||
}
|
||||
}
|
||||
|
||||
else if (this.Visible)
|
||||
{
|
||||
// Otherwise just update the existing sample fields
|
||||
UpdateChangedSampleFields(this.RootRow, samples, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function GrowGrid(parent_row, nb_samples)
|
||||
{
|
||||
parent_row.Rows.Clear();
|
||||
|
||||
for (var i = 0; i < nb_samples; i++)
|
||||
{
|
||||
var cell_data =
|
||||
{
|
||||
_ID: i,
|
||||
Name: "",
|
||||
Length: "",
|
||||
Self: "",
|
||||
Calls: "",
|
||||
Recurse: "",
|
||||
};
|
||||
|
||||
var cell_classes =
|
||||
{
|
||||
Name: "SampleNameCell",
|
||||
Length: "SampleTimeCell",
|
||||
Self: "SampleTimeCell",
|
||||
Calls: "SampleCountCell",
|
||||
Recurse: "SampleCountCell",
|
||||
};
|
||||
|
||||
parent_row.Rows.Add(cell_data, null, cell_classes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function UpdateAllSampleFields(parent_row, samples, index, indent)
|
||||
{
|
||||
for (var i in samples)
|
||||
{
|
||||
var sample = samples[i];
|
||||
|
||||
// Match row allocation in GrowGrid
|
||||
var row = parent_row.Rows.Rows[index++];
|
||||
|
||||
// Sample row may have been hidden previously
|
||||
DOM.Node.Show(row.Node);
|
||||
|
||||
// Assign unique ID so that the common fast path of updating sample times only
|
||||
// can lookup target samples in the grid
|
||||
row.CellData._ID = sample.id;
|
||||
parent_row.Rows.AddRowToIndex("_ID", sample.id, row);
|
||||
|
||||
// Record sample name for later comparison
|
||||
row.CellData.Name = sample.name.string;
|
||||
|
||||
// Set sample name and colour
|
||||
var name_node = row.CellNodes["Name"];
|
||||
name_node.innerHTML = indent + sample.name.string;
|
||||
DOM.Node.SetColour(name_node, sample.colour);
|
||||
|
||||
row.CellNodes["Length"].innerHTML = sample.ms_length;
|
||||
row.CellNodes["Self"].innerHTML = sample.ms_self;
|
||||
row.CellNodes["Calls"].innerHTML = sample.call_count;
|
||||
row.CellNodes["Recurse"].innerHTML = sample.recurse_depth;
|
||||
|
||||
index = UpdateAllSampleFields(parent_row, sample.children, index, indent + " ");
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
function UpdateChangedSampleFields(parent_row, samples, indent)
|
||||
{
|
||||
for (var i in samples)
|
||||
{
|
||||
var sample = samples[i];
|
||||
|
||||
var row = parent_row.Rows.GetBy("_ID", sample.id);
|
||||
if (row)
|
||||
{
|
||||
row.CellNodes["Length"].innerHTML = sample.ms_length;
|
||||
row.CellNodes["Self"].innerHTML = sample.ms_self;
|
||||
row.CellNodes["Calls"].innerHTML = sample.call_count;
|
||||
row.CellNodes["Recurse"].innerHTML = sample.recurse_depth;
|
||||
|
||||
// Sample name will change when it switches from hash ID to network-retrieved
|
||||
// name. Quickly check that before re-applying the HTML for the name.
|
||||
if (row.CellData.Name != sample.name.string)
|
||||
{
|
||||
var name_node = row.CellNodes["Name"];
|
||||
row.CellData.Name = sample.name.string;
|
||||
name_node.innerHTML = indent + sample.name.string;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateChangedSampleFields(parent_row, sample.children, indent + " ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return SampleWindow;
|
||||
})();
|
|
@ -1,28 +0,0 @@
|
|||
|
||||
|
||||
ThreadFrame = (function()
|
||||
{
|
||||
function ThreadFrame(message)
|
||||
{
|
||||
// Persist the required message data
|
||||
this.NbSamples = message.nb_samples;
|
||||
this.SampleDigest = message.sample_digest;
|
||||
this.Samples = message.samples;
|
||||
|
||||
// Calculate the frame start/end times
|
||||
this.StartTime_us = 0;
|
||||
this.EndTime_us = 0;
|
||||
var nb_root_samples = this.Samples.length;
|
||||
if (nb_root_samples > 0)
|
||||
{
|
||||
var last_sample = this.Samples[nb_root_samples - 1];
|
||||
this.StartTime_us = this.Samples[0].us_start;
|
||||
this.EndTime_us = last_sample.us_start + last_sample.us_length;
|
||||
}
|
||||
|
||||
this.Length_us = this.EndTime_us - this.StartTime_us;
|
||||
}
|
||||
|
||||
|
||||
return ThreadFrame;
|
||||
})();
|
|
@ -1,379 +0,0 @@
|
|||
|
||||
|
||||
TimelineRow = (function()
|
||||
{
|
||||
var row_template = function(){/*
|
||||
<div class='TimelineRow'>
|
||||
<div class='TimelineRowCheck TimelineBox'>
|
||||
<input class='TimelineRowCheckbox' type='checkbox' />
|
||||
</div>
|
||||
<div class='TimelineRowExpand TimelineBox NoSelect'>
|
||||
<div class='TimelineRowExpandButton'>+</div>
|
||||
</div>
|
||||
<div class='TimelineRowExpand TimelineBox NoSelect'>
|
||||
<div class='TimelineRowExpandButton'>-</div>
|
||||
</div>
|
||||
<div class='TimelineRowLabel TimelineBox'></div>
|
||||
<canvas class='TimelineRowCanvas'></canvas>
|
||||
<div style="clear:left"></div>
|
||||
</div>
|
||||
*/}.toString().split(/\n/).slice(1, -1).join("\n");
|
||||
|
||||
|
||||
var CANVAS_Y_OFFSET = 0;
|
||||
var CANVAS_BORDER = 1;
|
||||
var SAMPLE_HEIGHT = 16;
|
||||
var SAMPLE_BORDER = 1;
|
||||
var SAMPLE_Y_SPACING = SAMPLE_HEIGHT + SAMPLE_BORDER * 2;
|
||||
var SAMPLE_Y_OFFSET = CANVAS_Y_OFFSET + CANVAS_BORDER + 1;
|
||||
|
||||
|
||||
function TimelineRow(name, width, parent_node, frame_history, check_handler)
|
||||
{
|
||||
this.Name = name;
|
||||
|
||||
// Create the row HTML and add to the parent
|
||||
this.ContainerNode = DOM.Node.CreateHTML(row_template);
|
||||
this.Node = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowData");
|
||||
this.LabelNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowLabel");
|
||||
this.LabelNode.innerHTML = name;
|
||||
this.CheckboxNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCheckbox");
|
||||
var expand_node_0 = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowExpand", 0);
|
||||
var expand_node_1 = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowExpand", 1);
|
||||
this.IncNode = DOM.Node.FindWithClass(expand_node_0, "TimelineRowExpandButton");
|
||||
this.DecNode = DOM.Node.FindWithClass(expand_node_1, "TimelineRowExpandButton");
|
||||
this.CanvasNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCanvas");
|
||||
parent_node.appendChild(this.ContainerNode);
|
||||
|
||||
// All sample view windows visible by default
|
||||
this.CheckboxNode.checked = true;
|
||||
DOM.Event.AddHandler(this.CheckboxNode, "change", function(evt) { check_handler(name, evt); });
|
||||
|
||||
// Manually hook-up events to simulate div:active
|
||||
// I can't get the equivalent CSS to work in Firefox, so...
|
||||
DOM.Event.AddHandler(this.IncNode, "mousedown", ExpandButtonDown);
|
||||
DOM.Event.AddHandler(this.IncNode, "mouseup", ExpandButtonUp);
|
||||
DOM.Event.AddHandler(this.IncNode, "mouseleave", ExpandButtonUp);
|
||||
DOM.Event.AddHandler(this.DecNode, "mousedown", ExpandButtonDown);
|
||||
DOM.Event.AddHandler(this.DecNode, "mouseup", ExpandButtonUp);
|
||||
DOM.Event.AddHandler(this.DecNode, "mouseleave", ExpandButtonUp);
|
||||
|
||||
// Pressing +/i increases/decreases depth
|
||||
DOM.Event.AddHandler(this.IncNode, "click", Bind(IncDepth, this));
|
||||
DOM.Event.AddHandler(this.DecNode, "click", Bind(DecDepth, this));
|
||||
|
||||
// Setup the canvas
|
||||
this.Depth = 1;
|
||||
this.Ctx = this.CanvasNode.getContext("2d");
|
||||
this.SetSize(width);
|
||||
this.Clear();
|
||||
|
||||
// Frame index to start at when looking for first visible sample
|
||||
this.StartFrameIndex = 0;
|
||||
|
||||
this.FrameHistory = frame_history;
|
||||
this.VisibleFrames = [ ];
|
||||
this.VisibleTimeRange = null;
|
||||
|
||||
// Sample the mouse is currently hovering over
|
||||
this.HoverSample = null;
|
||||
this.HoverSampleDepth = 0;
|
||||
|
||||
// Currently selected sample
|
||||
this.SelectedSample = null;
|
||||
this.SelectedSampleDepth = 0;
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.SetSize = function(width)
|
||||
{
|
||||
// Must ALWAYS set the width/height properties together. Setting one on its own has weird side-effects.
|
||||
this.CanvasNode.width = width;
|
||||
this.CanvasNode.height = CANVAS_BORDER + SAMPLE_BORDER + SAMPLE_Y_SPACING * this.Depth;
|
||||
this.Draw(true);
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.Clear = function()
|
||||
{
|
||||
// Fill box that shows the boundary between thread rows
|
||||
this.Ctx.fillStyle = "#666"
|
||||
var b = CANVAS_BORDER;
|
||||
this.Ctx.fillRect(b, b, this.CanvasNode.width - b * 2, this.CanvasNode.height - b * 2);
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.SetVisibleFrames = function(time_range)
|
||||
{
|
||||
// Clear previous visible list
|
||||
this.VisibleFrames = [ ];
|
||||
if (this.FrameHistory.length == 0)
|
||||
return;
|
||||
|
||||
// Store a copy of the visible time range rather than referencing it
|
||||
// This prevents external modifications to the time range from affecting rendering/selection
|
||||
time_range = time_range.Clone();
|
||||
this.VisibleTimeRange = time_range;
|
||||
|
||||
// The frame history can be reset outside this class
|
||||
// This also catches the overflow to the end of the frame list below when a thread stops sending samples
|
||||
var max_frame = Math.max(this.FrameHistory.length - 1, 0);
|
||||
var start_frame_index = Math.min(this.StartFrameIndex, max_frame);
|
||||
|
||||
// First do a back-track in case the time range moves negatively
|
||||
while (start_frame_index > 0)
|
||||
{
|
||||
var frame = this.FrameHistory[start_frame_index];
|
||||
if (time_range.Start_us > frame.StartTime_us)
|
||||
break;
|
||||
start_frame_index--;
|
||||
}
|
||||
|
||||
// Then search from this point for the first visible frame
|
||||
while (start_frame_index < this.FrameHistory.length)
|
||||
{
|
||||
var frame = this.FrameHistory[start_frame_index];
|
||||
if (frame.EndTime_us > time_range.Start_us)
|
||||
break;
|
||||
start_frame_index++;
|
||||
}
|
||||
|
||||
// Gather all frames up to the end point
|
||||
this.StartFrameIndex = start_frame_index;
|
||||
for (var i = start_frame_index; i < this.FrameHistory.length; i++)
|
||||
{
|
||||
var frame = this.FrameHistory[i];
|
||||
if (frame.StartTime_us > time_range.End_us)
|
||||
break;
|
||||
this.VisibleFrames.push(frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.Draw = function(draw_text)
|
||||
{
|
||||
this.Clear();
|
||||
|
||||
// Draw all root samples in the visible frame set
|
||||
for (var i in this.VisibleFrames)
|
||||
{
|
||||
var frame = this.VisibleFrames[i];
|
||||
DrawSamples(this, frame.Samples, 1, draw_text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function DrawSamples(self, samples, depth, draw_text)
|
||||
{
|
||||
for (var i in samples)
|
||||
{
|
||||
var sample = samples[i];
|
||||
DrawSample(self, sample, depth, draw_text);
|
||||
|
||||
if (depth < self.Depth && sample.children != null)
|
||||
DrawSamples(self, sample.children, depth + 1, draw_text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.UpdateHoverSample = function(mouse_state, x_offset)
|
||||
{
|
||||
var hover = GetSampleAtPosition(this, mouse_state, x_offset);
|
||||
if (hover)
|
||||
this.SetHoverSample(hover[1], hover[2]);
|
||||
return hover;
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.UpdateSelectedSample = function(mouse_state, x_offset)
|
||||
{
|
||||
var select = GetSampleAtPosition(this, mouse_state, x_offset);
|
||||
if (select)
|
||||
this.SetSelectedSample(select[1], select[2]);
|
||||
return select;
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.SetHoverSample = function(sample, sample_depth)
|
||||
{
|
||||
if (sample != this.HoverSample)
|
||||
{
|
||||
// Discard old highlight
|
||||
// TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate
|
||||
var old_sample = this.HoverSample;
|
||||
var old_sample_depth = this.HoverSampleDepth;
|
||||
this.HoverSample = null;
|
||||
this.HoverSampleDepth = 0;
|
||||
DrawSample(this, old_sample, old_sample_depth, true);
|
||||
|
||||
// Add new highlight
|
||||
this.HoverSample = sample;
|
||||
this.HoverSampleDepth = sample_depth;
|
||||
DrawSample(this, sample, sample_depth, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TimelineRow.prototype.SetSelectedSample = function(sample, sample_depth)
|
||||
{
|
||||
if (sample != this.SelectedSample)
|
||||
{
|
||||
// Discard old highlight
|
||||
// TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate
|
||||
var old_sample = this.SelectedSample;
|
||||
var old_sample_depth = this.SelectedSampleDepth;
|
||||
this.SelectedSample = null;
|
||||
this.SelectedSampleDepth = 0;
|
||||
DrawSample(this, old_sample, old_sample_depth, true);
|
||||
|
||||
// Add new highlight
|
||||
this.SelectedSample = sample;
|
||||
this.SelectedSampleDepth = sample_depth;
|
||||
DrawSample(this, sample, sample_depth, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ExpandButtonDown(evt)
|
||||
{
|
||||
var node = DOM.Event.GetNode(evt);
|
||||
DOM.Node.AddClass(node, "TimelineRowExpandButtonActive");
|
||||
}
|
||||
|
||||
|
||||
function ExpandButtonUp(evt)
|
||||
{
|
||||
var node = DOM.Event.GetNode(evt);
|
||||
DOM.Node.RemoveClass(node, "TimelineRowExpandButtonActive");
|
||||
}
|
||||
|
||||
|
||||
function IncDepth(self)
|
||||
{
|
||||
self.Depth++;
|
||||
self.SetSize(self.CanvasNode.width);
|
||||
}
|
||||
|
||||
|
||||
function DecDepth(self)
|
||||
{
|
||||
if (self.Depth > 1)
|
||||
{
|
||||
self.Depth--;
|
||||
self.SetSize(self.CanvasNode.width);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function GetSampleAtPosition(self, mouse_state, x_offset)
|
||||
{
|
||||
// Mouse movement can occur before any data is sent to a timeline row
|
||||
var time_range = self.VisibleTimeRange;
|
||||
if (time_range == null)
|
||||
return;
|
||||
|
||||
// Get the time the mouse is over
|
||||
var x = mouse_state.Position[0] - x_offset;
|
||||
var time_us = time_range.Start_us + x / time_range.usPerPixel;
|
||||
|
||||
var canvas_y_offset = DOM.Node.GetPosition(self.CanvasNode)[1];
|
||||
var mouse_y_offset = mouse_state.Position[1] - canvas_y_offset;
|
||||
mouse_y_offset = Math.min(Math.max(mouse_y_offset, 0), self.CanvasNode.height);
|
||||
var depth = Math.floor(mouse_y_offset / SAMPLE_Y_SPACING) + 1;
|
||||
|
||||
// Search for the first frame to intersect this time
|
||||
for (var i in self.VisibleFrames)
|
||||
{
|
||||
var frame = self.VisibleFrames[i];
|
||||
if (time_us >= frame.StartTime_us && time_us < frame.EndTime_us)
|
||||
{
|
||||
var found_sample = FindSample(self, frame.Samples, time_us, depth, 1);
|
||||
if (found_sample != null)
|
||||
return [ frame, found_sample[0], found_sample[1] ];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function FindSample(self, samples, time_us, target_depth, depth)
|
||||
{
|
||||
for (var i in samples)
|
||||
{
|
||||
var sample = samples[i];
|
||||
if (depth == target_depth)
|
||||
{
|
||||
if (time_us >= sample.us_start && time_us < sample.us_start + sample.us_length)
|
||||
return [ sample, depth ];
|
||||
}
|
||||
|
||||
else if (depth < target_depth && sample.children != null)
|
||||
{
|
||||
var found_sample = FindSample(self, sample.children, time_us, target_depth, depth + 1);
|
||||
if (found_sample != null)
|
||||
return found_sample;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function DrawSample(self, sample, depth, draw_text)
|
||||
{
|
||||
if (sample == null)
|
||||
return;
|
||||
|
||||
// Determine pixel range of the sample
|
||||
var time_range = self.VisibleTimeRange;
|
||||
var x0 = time_range.PixelOffset(sample.us_start);
|
||||
var x1 = x0 + time_range.PixelSize(sample.us_length);
|
||||
|
||||
// Clip to padded timeline row
|
||||
var min_x = 3;
|
||||
var max_x = self.CanvasNode.width - 5;
|
||||
x0 = Math.min(Math.max(x0, min_x), max_x);
|
||||
x1 = Math.min(Math.max(x1, min_x), max_x);
|
||||
|
||||
var offset_x = x0;
|
||||
var offset_y = SAMPLE_Y_OFFSET + (depth - 1) * SAMPLE_Y_SPACING;
|
||||
var size_x = x1 - x0;
|
||||
var size_y = SAMPLE_HEIGHT;
|
||||
|
||||
// Normal rendering
|
||||
var ctx = self.Ctx;
|
||||
ctx.fillStyle = sample.colour;
|
||||
ctx.fillRect(offset_x, offset_y, size_x, size_y);
|
||||
|
||||
// Highlight rendering
|
||||
var b = (sample == self.HoverSample) ? 255 : 0;
|
||||
var r = (sample == self.SelectedSample) ? 255 : 0;
|
||||
if (b + r > 0)
|
||||
{
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = "rgb(" + r + ", 0, " + b + ")";
|
||||
ctx.strokeRect(offset_x + 0.5, offset_y + 0.5, size_x - 1, size_y - 1);
|
||||
}
|
||||
|
||||
// Draw sample names clipped to the bounds of the sample
|
||||
// Also reject tiny samples with no space to render text
|
||||
if (draw_text && size_x > 8)
|
||||
{
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(offset_x + 2.5, offset_y + 1.5, size_x - 5, size_y - 3);
|
||||
ctx.clip();
|
||||
ctx.font = "9px verdana";
|
||||
ctx.fillStyle = "black";
|
||||
var text = sample.name.string
|
||||
text += " (" + sample.ms_length + "ms";
|
||||
text += ", " + sample.call_count + "c)";
|
||||
ctx.fillText(text, offset_x + 5.5, offset_y + 1.5 + 9);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return TimelineRow;
|
||||
})();
|
|
@ -1,284 +0,0 @@
|
|||
|
||||
//
|
||||
// TODO: Use WebGL and instancing for quicker renders
|
||||
//
|
||||
|
||||
|
||||
TimelineWindow = (function()
|
||||
{
|
||||
var BORDER = 10;
|
||||
|
||||
var ROW_START_SIZE = 210;
|
||||
|
||||
var ROW_END_SIZE = 20; // make room for scrollbar
|
||||
|
||||
var box_template = "<div class='TimelineBox'></div>";
|
||||
|
||||
function TimelineWindow(wm, settings, server, check_handler)
|
||||
{
|
||||
this.Settings = settings;
|
||||
|
||||
// Ordered list of thread rows on the timeline
|
||||
this.ThreadRows = [ ];
|
||||
|
||||
// Create window and containers
|
||||
this.Window = wm.AddWindow("Timeline", 10, 20, 100, 100);
|
||||
this.Window.ShowNoAnim();
|
||||
this.TimelineContainer = this.Window.AddControlNew(new WM.Container(10, 10, 800, 160));
|
||||
DOM.Node.AddClass(this.TimelineContainer.Node, "TimelineContainer");
|
||||
|
||||
var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel";
|
||||
DOM.Event.AddHandler(this.TimelineContainer.Node, mouse_wheel_event, Bind(OnMouseScroll, this));
|
||||
|
||||
// Setup timeline manipulation
|
||||
this.MouseDown = false;
|
||||
this.LastMouseState = null;
|
||||
this.TimelineMoved = false;
|
||||
this.OnHoverHandler = null;
|
||||
this.OnSelectedHandler = null;
|
||||
DOM.Event.AddHandler(this.TimelineContainer.Node, "mousedown", Bind(OnMouseDown, this));
|
||||
DOM.Event.AddHandler(this.TimelineContainer.Node, "mouseup", Bind(OnMouseUp, this));
|
||||
DOM.Event.AddHandler(this.TimelineContainer.Node, "mousemove", Bind(OnMouseMove, this));
|
||||
|
||||
// Set time range AFTER the window has been created, as it uses the window to determine pixel coverage
|
||||
this.TimeRange = new PixelTimeRange(0, 200 * 1000, RowWidth(this));
|
||||
|
||||
this.CheckHandler = check_handler;
|
||||
|
||||
this.Window.SetOnResize(Bind(OnUserResize, this));
|
||||
}
|
||||
|
||||
|
||||
TimelineWindow.prototype.SetOnHover = function(handler)
|
||||
{
|
||||
this.OnHoverHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
TimelineWindow.prototype.SetOnSelected = function(handler)
|
||||
{
|
||||
this.OnSelectedHandler = handler;
|
||||
}
|
||||
|
||||
TimelineWindow.prototype.WindowResized = function(width, height, top_window)
|
||||
{
|
||||
// Resize window
|
||||
var top = top_window.Position[1] + top_window.Size[1] + 10;
|
||||
this.Window.SetPosition(10, top);
|
||||
this.Window.SetSize(width - 2 * 10, 260);
|
||||
|
||||
ResizeInternals(this);
|
||||
}
|
||||
|
||||
|
||||
TimelineWindow.prototype.ResetTimeRange = function()
|
||||
{
|
||||
this.TimeRange.SetStart(0);
|
||||
}
|
||||
|
||||
|
||||
TimelineWindow.prototype.OnSamples = function(thread_name, frame_history)
|
||||
{
|
||||
// Shift the timeline to the last entry on this thread
|
||||
// As multiple threads come through here with different end frames, only do this for the latest
|
||||
var last_frame = frame_history[frame_history.length - 1];
|
||||
if (last_frame.EndTime_us > this.TimeRange.End_us)
|
||||
this.TimeRange.SetEnd(last_frame.EndTime_us);
|
||||
|
||||
// Search for the index of this thread
|
||||
var thread_index = -1;
|
||||
for (var i in this.ThreadRows)
|
||||
{
|
||||
if (this.ThreadRows[i].Name == thread_name)
|
||||
{
|
||||
thread_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If this thread has not been seen before, add a new row to the list and re-sort
|
||||
if (thread_index == -1)
|
||||
{
|
||||
var row = new TimelineRow(thread_name, RowWidth(this), this.TimelineContainer.Node, frame_history, this.CheckHandler);
|
||||
this.ThreadRows.push(row);
|
||||
this.ThreadRows.sort(function(a, b) { return b.Name.localeCompare(a.Name); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TimelineWindow.prototype.DrawAllRows = function()
|
||||
{
|
||||
var time_range = this.TimeRange;
|
||||
var draw_text = this.Settings.IsPaused;
|
||||
for (var i in this.ThreadRows)
|
||||
{
|
||||
var thread_row = this.ThreadRows[i];
|
||||
thread_row.SetVisibleFrames(time_range);
|
||||
thread_row.Draw(draw_text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function RowXOffset(self)
|
||||
{
|
||||
// Add sizing of the label
|
||||
// TODO: Use computed size
|
||||
return DOM.Node.GetPosition(self.TimelineContainer.Node)[0] + ROW_START_SIZE;
|
||||
}
|
||||
|
||||
|
||||
function RowWidth(self)
|
||||
{
|
||||
// Subtract sizing of the label
|
||||
// TODO: Use computed size
|
||||
return self.TimelineContainer.Size[0] - (ROW_START_SIZE + ROW_END_SIZE);
|
||||
}
|
||||
|
||||
function OnUserResize(self, evt)
|
||||
{
|
||||
ResizeInternals(self);
|
||||
}
|
||||
|
||||
function ResizeInternals(self)
|
||||
{
|
||||
// Resize controls
|
||||
var parent_size = self.Window.Size;
|
||||
self.TimelineContainer.SetPosition(BORDER, 10);
|
||||
self.TimelineContainer.SetSize(parent_size[0] - 2 * BORDER, parent_size[1] - 40);
|
||||
|
||||
// Resize rows
|
||||
var row_width = RowWidth(self);
|
||||
for (var i in self.ThreadRows)
|
||||
{
|
||||
var row = self.ThreadRows[i];
|
||||
row.SetSize(row_width);
|
||||
}
|
||||
|
||||
// Adjust time range to new width
|
||||
self.TimeRange.SetPixelSpan(row_width);
|
||||
self.DrawAllRows();
|
||||
}
|
||||
|
||||
|
||||
function OnMouseScroll(self, evt)
|
||||
{
|
||||
var mouse_state = new Mouse.State(evt);
|
||||
var scale = 1.11;
|
||||
if (mouse_state.WheelDelta > 0)
|
||||
scale = 1 / scale;
|
||||
|
||||
// What time is the mouse hovering over?
|
||||
var x = mouse_state.Position[0] - RowXOffset(self);
|
||||
var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel;
|
||||
|
||||
// Calculate start time relative to the mouse hover position
|
||||
var time_start_us = self.TimeRange.Start_us - time_us;
|
||||
|
||||
// Scale and offset back to the hover time
|
||||
self.TimeRange.Set(time_start_us * scale + time_us, self.TimeRange.Span_us * scale);
|
||||
self.DrawAllRows();
|
||||
|
||||
// Prevent vertical scrolling on mouse-wheel
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
|
||||
|
||||
function OnMouseDown(self, evt)
|
||||
{
|
||||
// Only manipulate the timelime when paused
|
||||
if (!self.Settings.IsPaused)
|
||||
return;
|
||||
|
||||
self.MouseDown = true;
|
||||
self.LastMouseState = new Mouse.State(evt);
|
||||
self.TimelineMoved = false;
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
}
|
||||
|
||||
|
||||
function OnMouseUp(self, evt)
|
||||
{
|
||||
// Only manipulate the timelime when paused
|
||||
if (!self.Settings.IsPaused)
|
||||
return;
|
||||
|
||||
var mouse_state = new Mouse.State(evt);
|
||||
|
||||
self.MouseDown = false;
|
||||
|
||||
if (!self.TimelineMoved)
|
||||
{
|
||||
// Search for the row being clicked and update its selection
|
||||
var row_node = DOM.Event.GetNode(evt);
|
||||
for (var i in self.ThreadRows)
|
||||
{
|
||||
var thread_row = self.ThreadRows[i];
|
||||
if (thread_row.CanvasNode == row_node)
|
||||
{
|
||||
var select = thread_row.UpdateSelectedSample(mouse_state, RowXOffset(self));
|
||||
|
||||
// Call any selection handlers
|
||||
if (self.OnSelectedHandler)
|
||||
self.OnSelectedHandler(thread_row.Name, select);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnMouseMove(self, evt)
|
||||
{
|
||||
// Only manipulate the timelime when paused
|
||||
if (!self.Settings.IsPaused)
|
||||
return;
|
||||
|
||||
var mouse_state = new Mouse.State(evt);
|
||||
|
||||
if (self.MouseDown)
|
||||
{
|
||||
// Get the time the mouse is over
|
||||
var x = mouse_state.Position[0] - RowXOffset(self);
|
||||
var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel;
|
||||
|
||||
// Shift the visible time range with mouse movement
|
||||
var time_offset_us = (mouse_state.Position[0] - self.LastMouseState.Position[0]) / self.TimeRange.usPerPixel;
|
||||
if (time_offset_us)
|
||||
{
|
||||
self.TimeRange.SetStart(self.TimeRange.Start_us - time_offset_us);
|
||||
self.DrawAllRows();
|
||||
self.TimelineMoved = true;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Highlight any samples the mouse moves over
|
||||
var row_node = DOM.Event.GetNode(evt);
|
||||
for (var i in self.ThreadRows)
|
||||
{
|
||||
var thread_row = self.ThreadRows[i];
|
||||
if (thread_row.CanvasNode == row_node)
|
||||
{
|
||||
var hover = thread_row.UpdateHoverSample(mouse_state, RowXOffset(self));
|
||||
|
||||
if (self.OnHoverHandler)
|
||||
self.OnHoverHandler(thread_row.Name, hover);
|
||||
}
|
||||
else
|
||||
{
|
||||
thread_row.SetHoverSample(null, 0);
|
||||
if (self.OnHoverHandler)
|
||||
self.OnHoverHandler(thread_row.Name, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.LastMouseState = mouse_state;
|
||||
}
|
||||
|
||||
|
||||
return TimelineWindow;
|
||||
})();
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
|
||||
TitleWindow = (function()
|
||||
{
|
||||
function TitleWindow(wm, settings, server, connection_address)
|
||||
{
|
||||
this.Settings = settings;
|
||||
|
||||
this.Window = wm.AddWindow(" Remotery", 10, 10, 100, 100);
|
||||
this.Window.ShowNoAnim();
|
||||
|
||||
this.PingContainer = this.Window.AddControlNew(new WM.Container(4, -13, 10, 10));
|
||||
DOM.Node.AddClass(this.PingContainer.Node, "PingContainer");
|
||||
|
||||
this.EditBox = this.Window.AddControlNew(new WM.EditBox(10, 5, 300, 18, "Connection Address", connection_address));
|
||||
|
||||
// Setup pause button
|
||||
this.PauseButton = this.Window.AddControlNew(new WM.Button("Pause", 5, 5, { toggle: true }));
|
||||
this.PauseButton.SetOnClick(Bind(OnPausePressed, this));
|
||||
|
||||
server.AddMessageHandler("PING", Bind(OnPing, this));
|
||||
|
||||
this.Window.SetOnResize(Bind(OnUserResize, this));
|
||||
}
|
||||
|
||||
|
||||
TitleWindow.prototype.SetConnectionAddressChanged = function(handler)
|
||||
{
|
||||
this.EditBox.SetChangeHandler(handler);
|
||||
}
|
||||
|
||||
|
||||
TitleWindow.prototype.WindowResized = function(width, height)
|
||||
{
|
||||
this.Window.SetSize(width - 2 * 10, 50);
|
||||
ResizeInternals(this);
|
||||
}
|
||||
|
||||
function OnUserResize(self, evt)
|
||||
{
|
||||
ResizeInternals(self);
|
||||
}
|
||||
|
||||
function ResizeInternals(self)
|
||||
{
|
||||
self.PauseButton.SetPosition(self.Window.Size[0] - 60, 5);
|
||||
}
|
||||
|
||||
|
||||
function OnPausePressed(self)
|
||||
{
|
||||
self.Settings.IsPaused = self.PauseButton.IsPressed();
|
||||
if (self.Settings.IsPaused)
|
||||
self.PauseButton.SetText("Paused");
|
||||
else
|
||||
self.PauseButton.SetText("Pause");
|
||||
}
|
||||
|
||||
|
||||
function OnPing(self, server)
|
||||
{
|
||||
// Set the ping container as active and take it off half a second later
|
||||
DOM.Node.AddClass(self.PingContainer.Node, "PingContainerActive");
|
||||
window.setTimeout(Bind(function(self)
|
||||
{
|
||||
DOM.Node.RemoveClass(self.PingContainer.Node, "PingContainerActive");
|
||||
}, self), 500);
|
||||
}
|
||||
|
||||
|
||||
return TitleWindow;
|
||||
})();
|
|
@ -1,137 +0,0 @@
|
|||
|
||||
WebSocketConnection = (function()
|
||||
{
|
||||
function WebSocketConnection()
|
||||
{
|
||||
this.MessageHandlers = { };
|
||||
this.Socket = null;
|
||||
this.Console = null;
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.SetConsole = function(console)
|
||||
{
|
||||
this.Console = console;
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.Connected = function()
|
||||
{
|
||||
// Will return true if the socket is also in the process of connecting
|
||||
return this.Socket != null;
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.AddConnectHandler = function(handler)
|
||||
{
|
||||
this.AddMessageHandler("__OnConnect__", handler);
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.AddDisconnectHandler = function(handler)
|
||||
{
|
||||
this.AddMessageHandler("__OnDisconnect__", handler);
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.AddMessageHandler = function(message_name, handler)
|
||||
{
|
||||
// Create the message handler array on-demand
|
||||
if (!(message_name in this.MessageHandlers))
|
||||
this.MessageHandlers[message_name] = [ ];
|
||||
this.MessageHandlers[message_name].push(handler);
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.Connect = function(address)
|
||||
{
|
||||
// Disconnect if already connected
|
||||
if (this.Connected())
|
||||
this.Disconnect();
|
||||
|
||||
Log(this, "Connecting to " + address);
|
||||
|
||||
this.Socket = new WebSocket(address);
|
||||
this.Socket.binaryType = "arraybuffer";
|
||||
this.Socket.onopen = Bind(OnOpen, this);
|
||||
this.Socket.onmessage = Bind(OnMessage, this);
|
||||
this.Socket.onclose = Bind(OnClose, this);
|
||||
this.Socket.onerror = Bind(OnError, this);
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.Disconnect = function()
|
||||
{
|
||||
Log(this, "Disconnecting");
|
||||
if (this.Connected())
|
||||
this.Socket.close();
|
||||
}
|
||||
|
||||
|
||||
WebSocketConnection.prototype.Send = function(msg)
|
||||
{
|
||||
if (this.Connected())
|
||||
this.Socket.send(msg);
|
||||
}
|
||||
|
||||
|
||||
function Log(self, message)
|
||||
{
|
||||
self.Console.Log(message);
|
||||
}
|
||||
|
||||
|
||||
function CallMessageHandlers(self, message_name, data_view)
|
||||
{
|
||||
if (message_name in self.MessageHandlers)
|
||||
{
|
||||
var handlers = self.MessageHandlers[message_name];
|
||||
for (var i in handlers)
|
||||
handlers[i](self, data_view);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnOpen(self, event)
|
||||
{
|
||||
Log(self, "Connected");
|
||||
CallMessageHandlers(self, "__OnConnect__");
|
||||
}
|
||||
|
||||
|
||||
function OnClose(self, event)
|
||||
{
|
||||
// Clear all references
|
||||
self.Socket.onopen = null;
|
||||
self.Socket.onmessage = null;
|
||||
self.Socket.onclose = null;
|
||||
self.Socket.onerror = null;
|
||||
self.Socket = null;
|
||||
|
||||
Log(self, "Disconnected");
|
||||
CallMessageHandlers(self, "__OnDisconnect__");
|
||||
}
|
||||
|
||||
|
||||
function OnError(self, event)
|
||||
{
|
||||
Log(self, "Connection Error ");
|
||||
}
|
||||
|
||||
|
||||
function OnMessage(self, event)
|
||||
{
|
||||
var data_view = new DataView(event.data);
|
||||
|
||||
var id = String.fromCharCode(
|
||||
data_view.getInt8(0),
|
||||
data_view.getInt8(1),
|
||||
data_view.getInt8(2),
|
||||
data_view.getInt8(3));
|
||||
|
||||
CallMessageHandlers(self, id, data_view);
|
||||
}
|
||||
|
||||
|
||||
return WebSocketConnection;
|
||||
})();
|
|
@ -1,234 +0,0 @@
|
|||
|
||||
body
|
||||
{
|
||||
/* Take up the full page */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
|
||||
background-color: #999;
|
||||
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
|
||||
/* Override default container style to remove 3D effect */
|
||||
.Container
|
||||
{
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
/* Override default edit box style to remove 3D effect */
|
||||
.EditBox
|
||||
{
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
width:200;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ConsoleText
|
||||
{
|
||||
overflow:auto;
|
||||
color: #BBB;
|
||||
font: 9px Verdana;
|
||||
margin: 2px;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
|
||||
.PingContainer
|
||||
{
|
||||
background-color: #F55;
|
||||
border-radius: 2px;
|
||||
|
||||
/* Transition from green is gradual */
|
||||
transition: background-color 0.25s ease-in;
|
||||
}
|
||||
|
||||
|
||||
.PingContainerActive
|
||||
{
|
||||
background-color: #5F5;
|
||||
|
||||
/* Transition to green is instant */
|
||||
transition: none;
|
||||
}
|
||||
|
||||
|
||||
.SampleNameCell
|
||||
{
|
||||
width:243px;
|
||||
}
|
||||
.SampleTimeCell
|
||||
{
|
||||
width:52px;
|
||||
}
|
||||
.SampleCountCell
|
||||
{
|
||||
width:43px;
|
||||
}
|
||||
.SampleTitleNameCell
|
||||
{
|
||||
width:238px;
|
||||
|
||||
padding: 1px 1px 1px 2px;
|
||||
border: 1px solid;
|
||||
border-radius: 2px;
|
||||
|
||||
border-top-color:#555;
|
||||
border-left-color:#555;
|
||||
border-bottom-color:#111;
|
||||
border-right-color:#111;
|
||||
|
||||
background: #222;
|
||||
}
|
||||
.SampleTitleTimeCell
|
||||
{
|
||||
width:47px;
|
||||
|
||||
padding: 1px 1px 1px 2px;
|
||||
border: 1px solid;
|
||||
border-radius: 2px;
|
||||
|
||||
border-top-color:#555;
|
||||
border-left-color:#555;
|
||||
border-bottom-color:#111;
|
||||
border-right-color:#111;
|
||||
|
||||
background: #222;
|
||||
}
|
||||
.SampleTitleCountCell
|
||||
{
|
||||
width:38px;
|
||||
|
||||
padding: 1px 1px 1px 2px;
|
||||
border: 1px solid;
|
||||
border-radius: 2px;
|
||||
|
||||
border-top-color:#555;
|
||||
border-left-color:#555;
|
||||
border-bottom-color:#111;
|
||||
border-right-color:#111;
|
||||
|
||||
background: #222;
|
||||
}
|
||||
|
||||
|
||||
.TimelineBox
|
||||
{
|
||||
/* Following style generally copies GridRowCell.GridGroup from BrowserLib */
|
||||
|
||||
padding: 1px 1px 1px 2px;
|
||||
margin: 1px;
|
||||
|
||||
border: 1px solid;
|
||||
border-radius: 2px;
|
||||
border-top-color:#555;
|
||||
border-left-color:#555;
|
||||
border-bottom-color:#111;
|
||||
border-right-color:#111;
|
||||
|
||||
background: #222;
|
||||
|
||||
font: 9px Verdana;
|
||||
color: #BBB;
|
||||
}
|
||||
.TimelineRow
|
||||
{
|
||||
width: 100%;
|
||||
}
|
||||
.TimelineRowCheckbox
|
||||
{
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin: 0px;
|
||||
}
|
||||
.TimelineRowCheck
|
||||
{
|
||||
/* Pull .TimelineRowExpand to the right of the checkbox */
|
||||
float:left;
|
||||
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.TimelineRowExpand
|
||||
{
|
||||
/* Pull .TimelineRowLabel to the right of +/- buttons */
|
||||
float:left;
|
||||
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.TimelineRowExpandButton
|
||||
{
|
||||
width: 11px;
|
||||
height: 12px;
|
||||
|
||||
color: #333;
|
||||
|
||||
border: 1px solid;
|
||||
|
||||
border-top-color:#F4F4F4;
|
||||
border-left-color:#F4F4F4;
|
||||
border-bottom-color:#8E8F8F;
|
||||
border-right-color:#8E8F8F;
|
||||
|
||||
/* Top-right to bottom-left grey background gradient */
|
||||
background: #f6f6f6; /* Old browsers */
|
||||
background: -moz-linear-gradient(-45deg, #f6f6f6 0%, #abaeb2 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#f6f6f6), color-stop(100%,#abaeb2)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* Opera 11.10+ */
|
||||
background: -ms-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* IE10+ */
|
||||
background: linear-gradient(135deg, #f6f6f6 0%,#abaeb2 100%); /* W3C */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f6f6f6', endColorstr='#abaeb2',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
|
||||
|
||||
text-align: center;
|
||||
vertical-align: center;
|
||||
}
|
||||
.TimelineRowExpandButton:hover
|
||||
{
|
||||
border-top-color:#79C6F9;
|
||||
border-left-color:#79C6F9;
|
||||
border-bottom-color:#385D72;
|
||||
border-right-color:#385D72;
|
||||
|
||||
/* Top-right to bottom-left blue background gradient, matching border */
|
||||
background: #f3f3f3; /* Old browsers */
|
||||
background: -moz-linear-gradient(-45deg, #f3f3f3 0%, #79c6f9 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#f3f3f3), color-stop(100%,#79c6f9)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* Opera 11.10+ */
|
||||
background: -ms-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* IE10+ */
|
||||
background: linear-gradient(135deg, #f3f3f3 0%,#79c6f9 100%); /* W3C */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f3f3f3', endColorstr='#79c6f9',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
|
||||
}
|
||||
.TimelineRowExpandButtonActive
|
||||
{
|
||||
/* Simple means of shifting text within a div to the bottom-right */
|
||||
padding-left:1px;
|
||||
padding-top:1px;
|
||||
width:10px;
|
||||
height:11px;
|
||||
}
|
||||
.TimelineRowLabel
|
||||
{
|
||||
/* Pull .TimelineRowCanvas to the right of the label */
|
||||
float:left;
|
||||
|
||||
width: 140px;
|
||||
height: 14px;
|
||||
}
|
||||
.TimelineRowCanvas
|
||||
{
|
||||
}
|
||||
|
||||
/* enable vertical scrollbar in TimelineContainer (useful for many threads) */
|
||||
.TimelineContainer
|
||||
{
|
||||
overflow-y: auto;
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
|
||||
//
|
||||
// Very basic linear value animation system, for now.
|
||||
//
|
||||
|
||||
|
||||
namespace("Anim");
|
||||
|
||||
|
||||
Anim.Animation = (function()
|
||||
{
|
||||
var anim_hz = 60;
|
||||
|
||||
|
||||
function Animation(anim_func, start_value, end_value, time, end_callback)
|
||||
{
|
||||
// Setup initial parameters
|
||||
this.StartValue = start_value;
|
||||
this.EndValue = end_value;
|
||||
this.ValueInc = (end_value - start_value) / (time * anim_hz);
|
||||
this.Value = start_value;
|
||||
this.Complete = false;
|
||||
this.EndCallback = end_callback;
|
||||
|
||||
// Cache the update function to prevent recreating the closure
|
||||
var self = this;
|
||||
this.AnimFunc = anim_func;
|
||||
this.AnimUpdate = function() { Update(self); }
|
||||
|
||||
// Call for the start value
|
||||
this.AnimUpdate();
|
||||
}
|
||||
|
||||
|
||||
function Update(self)
|
||||
{
|
||||
// Queue up the next frame immediately
|
||||
var id = window.setTimeout(self.AnimUpdate, 1000 / anim_hz);
|
||||
|
||||
// Linear step the value and check for completion
|
||||
self.Value += self.ValueInc;
|
||||
if (Math.abs(self.Value - self.EndValue) < 0.01)
|
||||
{
|
||||
self.Value = self.EndValue;
|
||||
self.Complete = true;
|
||||
|
||||
if (self.EndCallback)
|
||||
self.EndCallback();
|
||||
|
||||
window.clearTimeout(id);
|
||||
}
|
||||
|
||||
// Pass to the animation function
|
||||
self.AnimFunc(self.Value);
|
||||
}
|
||||
|
||||
|
||||
return Animation;
|
||||
})();
|
||||
|
||||
|
||||
Anim.Animate = function(anim_func, start_value, end_value, time, end_callback)
|
||||
{
|
||||
return new Anim.Animation(anim_func, start_value, end_value, time, end_callback);
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
//
|
||||
// This will generate a closure for the given function and optionally bind an arbitrary number of
|
||||
// its initial arguments to specific values.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// 0: Either the function scope or the function.
|
||||
// 1: If 0 is the function scope, this is the function.
|
||||
// Otherwise it's the start of the optional bound argument list.
|
||||
// 2: Start of the optional bound argument list if 1 is the function.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// function GlobalFunction(p0, p1, p2) { }
|
||||
// function ThisFunction(p0, p1, p2) { }
|
||||
//
|
||||
// var a = Bind("GlobalFunction");
|
||||
// var b = Bind(this, "ThisFunction");
|
||||
// var c = Bind("GlobalFunction", BoundParam0, BoundParam1);
|
||||
// var d = Bind(this, "ThisFunction", BoundParam0, BoundParam1);
|
||||
// var e = Bind(GlobalFunction);
|
||||
// var f = Bind(this, ThisFunction);
|
||||
// var g = Bind(GlobalFunction, BoundParam0, BoundParam1);
|
||||
// var h = Bind(this, ThisFunction, BoundParam0, BoundParam1);
|
||||
//
|
||||
// a(0, 1, 2);
|
||||
// b(0, 1, 2);
|
||||
// c(2);
|
||||
// d(2);
|
||||
// e(0, 1, 2);
|
||||
// f(0, 1, 2);
|
||||
// g(2);
|
||||
// h(2);
|
||||
//
|
||||
function Bind()
|
||||
{
|
||||
// No closure to define?
|
||||
if (arguments.length == 0)
|
||||
return null;
|
||||
|
||||
// Figure out which of the 4 call types is being used to bind
|
||||
// Locate scope, function and bound parameter start index
|
||||
|
||||
if (typeof(arguments[0]) == "string")
|
||||
{
|
||||
var scope = window;
|
||||
var func = window[arguments[0]];
|
||||
var start = 1;
|
||||
}
|
||||
|
||||
else if (typeof(arguments[0]) == "function")
|
||||
{
|
||||
var scope = window;
|
||||
var func = arguments[0];
|
||||
var start = 1;
|
||||
}
|
||||
|
||||
else if (typeof(arguments[1]) == "string")
|
||||
{
|
||||
var scope = arguments[0];
|
||||
var func = scope[arguments[1]];
|
||||
var start = 2;
|
||||
}
|
||||
|
||||
else if (typeof(arguments[1]) == "function")
|
||||
{
|
||||
var scope = arguments[0];
|
||||
var func = arguments[1];
|
||||
var start = 2;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// unknown
|
||||
console.log("Bind() ERROR: Unknown bind parameter configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the arguments list to an array
|
||||
var arg_array = Array.prototype.slice.call(arguments, start);
|
||||
start = arg_array.length;
|
||||
|
||||
return function()
|
||||
{
|
||||
// Concatenate incoming arguments
|
||||
for (var i = 0; i < arguments.length; i++)
|
||||
arg_array[start + i] = arguments[i];
|
||||
|
||||
// Call the function in the given scope with the new arguments
|
||||
return func.apply(scope, arg_array);
|
||||
}
|
||||
}
|
|
@ -1,218 +0,0 @@
|
|||
|
||||
namespace("Convert");
|
||||
|
||||
|
||||
//
|
||||
// Convert between utf8 and b64 without raising character out of range exceptions with unicode strings
|
||||
// Technique described here: http://monsur.hossa.in/2012/07/20/utf-8-in-javascript.html
|
||||
//
|
||||
Convert.utf8string_to_b64string = function(str)
|
||||
{
|
||||
return btoa(unescape(encodeURIComponent(str)));
|
||||
}
|
||||
Convert.b64string_to_utf8string = function(str)
|
||||
{
|
||||
return decodeURIComponent(escape(atob(str)));
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// More general approach, converting between byte arrays and b64
|
||||
// Info here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
|
||||
//
|
||||
Convert.b64string_to_Uint8Array = function(sBase64, nBlocksSize)
|
||||
{
|
||||
function b64ToUint6 (nChr)
|
||||
{
|
||||
return nChr > 64 && nChr < 91 ?
|
||||
nChr - 65
|
||||
: nChr > 96 && nChr < 123 ?
|
||||
nChr - 71
|
||||
: nChr > 47 && nChr < 58 ?
|
||||
nChr + 4
|
||||
: nChr === 43 ?
|
||||
62
|
||||
: nChr === 47 ?
|
||||
63
|
||||
:
|
||||
0;
|
||||
}
|
||||
|
||||
var
|
||||
sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""),
|
||||
nInLen = sB64Enc.length,
|
||||
nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2,
|
||||
taBytes = new Uint8Array(nOutLen);
|
||||
|
||||
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++)
|
||||
{
|
||||
nMod4 = nInIdx & 3;
|
||||
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
|
||||
if (nMod4 === 3 || nInLen - nInIdx === 1)
|
||||
{
|
||||
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++)
|
||||
taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return taBytes;
|
||||
}
|
||||
Convert.Uint8Array_to_b64string = function(aBytes)
|
||||
{
|
||||
function uint6ToB64 (nUint6)
|
||||
{
|
||||
return nUint6 < 26 ?
|
||||
nUint6 + 65
|
||||
: nUint6 < 52 ?
|
||||
nUint6 + 71
|
||||
: nUint6 < 62 ?
|
||||
nUint6 - 4
|
||||
: nUint6 === 62 ?
|
||||
43
|
||||
: nUint6 === 63 ?
|
||||
47
|
||||
:
|
||||
65;
|
||||
}
|
||||
|
||||
var nMod3, sB64Enc = "";
|
||||
|
||||
for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++)
|
||||
{
|
||||
nMod3 = nIdx % 3;
|
||||
if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0)
|
||||
sB64Enc += "\r\n";
|
||||
nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
|
||||
if (nMod3 === 2 || aBytes.length - nIdx === 1)
|
||||
{
|
||||
sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63));
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return sB64Enc.replace(/A(?=A$|$)/g, "=");
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Unicode and arbitrary value safe conversion between strings and Uint8Arrays
|
||||
//
|
||||
Convert.Uint8Array_to_string = function(aBytes)
|
||||
{
|
||||
var sView = "";
|
||||
|
||||
for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++)
|
||||
{
|
||||
nPart = aBytes[nIdx];
|
||||
sView += String.fromCharCode(
|
||||
nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */
|
||||
/* (nPart - 252 << 32) is not possible in ECMAScript! So...: */
|
||||
(nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
|
||||
: nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */
|
||||
(nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
|
||||
: nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */
|
||||
(nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
|
||||
: nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */
|
||||
(nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
|
||||
: nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */
|
||||
(nPart - 192 << 6) + aBytes[++nIdx] - 128
|
||||
: /* nPart < 127 ? */ /* one byte */
|
||||
nPart
|
||||
);
|
||||
}
|
||||
|
||||
return sView;
|
||||
}
|
||||
Convert.string_to_Uint8Array = function(sDOMStr)
|
||||
{
|
||||
var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0;
|
||||
|
||||
/* mapping... */
|
||||
|
||||
for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++)
|
||||
{
|
||||
nChr = sDOMStr.charCodeAt(nMapIdx);
|
||||
nArrLen += nChr < 0x80 ? 1 : nChr < 0x800 ? 2 : nChr < 0x10000 ? 3 : nChr < 0x200000 ? 4 : nChr < 0x4000000 ? 5 : 6;
|
||||
}
|
||||
|
||||
aBytes = new Uint8Array(nArrLen);
|
||||
|
||||
/* transcription... */
|
||||
|
||||
for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++)
|
||||
{
|
||||
nChr = sDOMStr.charCodeAt(nChrIdx);
|
||||
if (nChr < 128)
|
||||
{
|
||||
/* one byte */
|
||||
aBytes[nIdx++] = nChr;
|
||||
}
|
||||
else if (nChr < 0x800)
|
||||
{
|
||||
/* two bytes */
|
||||
aBytes[nIdx++] = 192 + (nChr >>> 6);
|
||||
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||
}
|
||||
else if (nChr < 0x10000)
|
||||
{
|
||||
/* three bytes */
|
||||
aBytes[nIdx++] = 224 + (nChr >>> 12);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||
}
|
||||
else if (nChr < 0x200000)
|
||||
{
|
||||
/* four bytes */
|
||||
aBytes[nIdx++] = 240 + (nChr >>> 18);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||
}
|
||||
else if (nChr < 0x4000000)
|
||||
{
|
||||
/* five bytes */
|
||||
aBytes[nIdx++] = 248 + (nChr >>> 24);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 18 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||
}
|
||||
else /* if (nChr <= 0x7fffffff) */
|
||||
{
|
||||
/* six bytes */
|
||||
aBytes[nIdx++] = 252 + /* (nChr >>> 32) is not possible in ECMAScript! So...: */ (nChr / 1073741824);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 24 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 18 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
|
||||
aBytes[nIdx++] = 128 + (nChr & 63);
|
||||
}
|
||||
}
|
||||
|
||||
return aBytes;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Converts all characters in a string that have equivalent entities to their ampersand/entity names.
|
||||
// Based on https://gist.github.com/jonathantneal/6093551
|
||||
//
|
||||
Convert.string_to_html_entities = (function()
|
||||
{
|
||||
'use strict';
|
||||
|
||||
var data = '34quot38amp39apos60lt62gt160nbsp161iexcl162cent163pound164curren165yen166brvbar167sect168uml169copy170ordf171laquo172not173shy174reg175macr176deg177plusmn178sup2179sup3180acute181micro182para183middot184cedil185sup1186ordm187raquo188frac14189frac12190frac34191iquest192Agrave193Aacute194Acirc195Atilde196Auml197Aring198AElig199Ccedil200Egrave201Eacute202Ecirc203Euml204Igrave205Iacute206Icirc207Iuml208ETH209Ntilde210Ograve211Oacute212Ocirc213Otilde214Ouml215times216Oslash217Ugrave218Uacute219Ucirc220Uuml221Yacute222THORN223szlig224agrave225aacute226acirc227atilde228auml229aring230aelig231ccedil232egrave233eacute234ecirc235euml236igrave237iacute238icirc239iuml240eth241ntilde242ograve243oacute244ocirc245otilde246ouml247divide248oslash249ugrave250uacute251ucirc252uuml253yacute254thorn255yuml402fnof913Alpha914Beta915Gamma916Delta917Epsilon918Zeta919Eta920Theta921Iota922Kappa923Lambda924Mu925Nu926Xi927Omicron928Pi929Rho931Sigma932Tau933Upsilon934Phi935Chi936Psi937Omega945alpha946beta947gamma948delta949epsilon950zeta951eta952theta953iota954kappa955lambda956mu957nu958xi959omicron960pi961rho962sigmaf963sigma964tau965upsilon966phi967chi968psi969omega977thetasym978upsih982piv8226bull8230hellip8242prime8243Prime8254oline8260frasl8472weierp8465image8476real8482trade8501alefsym8592larr8593uarr8594rarr8595darr8596harr8629crarr8656lArr8657uArr8658rArr8659dArr8660hArr8704forall8706part8707exist8709empty8711nabla8712isin8713notin8715ni8719prod8721sum8722minus8727lowast8730radic8733prop8734infin8736ang8743and8744or8745cap8746cup8747int8756there48764sim8773cong8776asymp8800ne8801equiv8804le8805ge8834sub8835sup8836nsub8838sube8839supe8853oplus8855otimes8869perp8901sdot8968lceil8969rceil8970lfloor8971rfloor9001lang9002rang9674loz9824spades9827clubs9829hearts9830diams338OElig339oelig352Scaron353scaron376Yuml710circ732tilde8194ensp8195emsp8201thinsp8204zwnj8205zwj8206lrm8207rlm8211ndash8212mdash8216lsquo8217rsquo8218sbquo8220ldquo8221rdquo8222bdquo8224dagger8225Dagger8240permil8249lsaquo8250rsaquo8364euro';
|
||||
var charCodes = data.split(/[A-z]+/);
|
||||
var entities = data.split(/\d+/).slice(1);
|
||||
|
||||
return function encodeHTMLEntities(text)
|
||||
{
|
||||
return text.replace(/[\u00A0-\u2666<>"'&]/g, function (match)
|
||||
{
|
||||
var charCode = String(match.charCodeAt(0));
|
||||
var index = charCodes.indexOf(charCode);
|
||||
return '&' + (entities[index] ? entities[index] : '#' + charCode) + ';';
|
||||
});
|
||||
};
|
||||
})();
|
|
@ -1,26 +0,0 @@
|
|||
|
||||
// TODO: requires function for checking existence of dependencies
|
||||
|
||||
|
||||
function namespace(name)
|
||||
{
|
||||
// Ensure all nested namespaces are created only once
|
||||
|
||||
var ns_list = name.split(".");
|
||||
var parent_ns = window;
|
||||
|
||||
for (var i in ns_list)
|
||||
{
|
||||
var ns_name = ns_list[i];
|
||||
if (!(ns_name in parent_ns))
|
||||
parent_ns[ns_name] = { };
|
||||
|
||||
parent_ns = parent_ns[ns_name];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function multiline(fn)
|
||||
{
|
||||
return fn.toString().split(/\n/).slice(1, -1).join("\n");
|
||||
}
|
|
@ -1,499 +0,0 @@
|
|||
|
||||
namespace("DOM.Node");
|
||||
namespace("DOM.Event");
|
||||
namespace("DOM.Applet");
|
||||
|
||||
|
||||
|
||||
//
|
||||
// =====================================================================================================================
|
||||
// ----- DOCUMENT NODE/ELEMENT EXTENSIONS ------------------------------------------------------------------------------
|
||||
// =====================================================================================================================
|
||||
//
|
||||
|
||||
|
||||
|
||||
DOM.Node.Get = function(id)
|
||||
{
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Set node position
|
||||
//
|
||||
DOM.Node.SetPosition = function(node, position)
|
||||
{
|
||||
node.style.left = position[0];
|
||||
node.style.top = position[1];
|
||||
}
|
||||
DOM.Node.SetX = function(node, x)
|
||||
{
|
||||
node.style.left = x;
|
||||
}
|
||||
DOM.Node.SetY = function(node, y)
|
||||
{
|
||||
node.style.top = y;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Get the absolute position of a HTML element on the page
|
||||
//
|
||||
DOM.Node.GetPosition = function(element, account_for_scroll)
|
||||
{
|
||||
// Recurse up through parents, summing offsets from their parent
|
||||
var x = 0, y = 0;
|
||||
for (var node = element; node != null; node = node.offsetParent)
|
||||
{
|
||||
x += node.offsetLeft;
|
||||
y += node.offsetTop;
|
||||
}
|
||||
|
||||
if (account_for_scroll)
|
||||
{
|
||||
// Walk up the hierarchy subtracting away any scrolling
|
||||
for (var node = element; node != document.body; node = node.parentNode)
|
||||
{
|
||||
x -= node.scrollLeft;
|
||||
y -= node.scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Set node size
|
||||
//
|
||||
DOM.Node.SetSize = function(node, size)
|
||||
{
|
||||
node.style.width = size[0];
|
||||
node.style.height = size[1];
|
||||
}
|
||||
DOM.Node.SetWidth = function(node, width)
|
||||
{
|
||||
node.style.width = width;
|
||||
}
|
||||
DOM.Node.SetHeight = function(node, height)
|
||||
{
|
||||
node.style.height = height;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Get node OFFSET size:
|
||||
// clientX includes padding
|
||||
// offsetX includes padding and borders
|
||||
// scrollX includes padding, borders and size of contained node
|
||||
//
|
||||
DOM.Node.GetSize = function(node)
|
||||
{
|
||||
return [ node.offsetWidth, node.offsetHeight ];
|
||||
}
|
||||
DOM.Node.GetWidth = function(node)
|
||||
{
|
||||
return node.offsetWidth;
|
||||
}
|
||||
DOM.Node.GetHeight = function(node)
|
||||
{
|
||||
return node.offsetHeight;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Set node opacity
|
||||
//
|
||||
DOM.Node.SetOpacity = function(node, value)
|
||||
{
|
||||
node.style.opacity = value;
|
||||
}
|
||||
|
||||
|
||||
DOM.Node.SetColour = function(node, colour)
|
||||
{
|
||||
node.style.color = colour;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Hide a node by completely disabling its rendering (it no longer contributes to document layout)
|
||||
//
|
||||
DOM.Node.Hide = function(node)
|
||||
{
|
||||
node.style.display = "none";
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Show a node by restoring its influcen in document layout
|
||||
//
|
||||
DOM.Node.Show = function(node)
|
||||
{
|
||||
node.style.display = "block";
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Add a CSS class to a HTML element, specified last
|
||||
//
|
||||
DOM.Node.AddClass = function(node, class_name)
|
||||
{
|
||||
// Ensure the class hasn't already been added
|
||||
DOM.Node.RemoveClass(node, class_name);
|
||||
node.className += " " + class_name;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Remove a CSS class from a HTML element
|
||||
//
|
||||
DOM.Node.RemoveClass = function(node, class_name)
|
||||
{
|
||||
// Remove all variations of where the class name can be in the string list
|
||||
var regexp = new RegExp("\\b" + class_name + "\\b");
|
||||
node.className = node.className.replace(regexp, "");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Check to see if a HTML element contains a class
|
||||
//
|
||||
DOM.Node.HasClass = function(node, class_name)
|
||||
{
|
||||
var regexp = new RegExp("\\b" + class_name + "\\b");
|
||||
return regexp.test(node.className);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Recursively search for a node with the given class name
|
||||
//
|
||||
DOM.Node.FindWithClass = function(parent_node, class_name, index)
|
||||
{
|
||||
// Search the children looking for a node with the given class name
|
||||
for (var i in parent_node.childNodes)
|
||||
{
|
||||
var node = parent_node.childNodes[i];
|
||||
if (DOM.Node.HasClass(node, class_name))
|
||||
{
|
||||
if (index === undefined || index-- == 0)
|
||||
return node;
|
||||
}
|
||||
|
||||
// Recurse into children
|
||||
node = DOM.Node.FindWithClass(node, class_name);
|
||||
if (node != null)
|
||||
return node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Check to see if one node logically contains another
|
||||
//
|
||||
DOM.Node.Contains = function(node, container_node)
|
||||
{
|
||||
while (node != null && node != container_node)
|
||||
node = node.parentNode;
|
||||
return node != null;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Create the HTML nodes specified in the text passed in
|
||||
// Assumes there is only one root node in the text
|
||||
//
|
||||
DOM.Node.CreateHTML = function(html)
|
||||
{
|
||||
var div = document.createElement("div");
|
||||
div.innerHTML = html;
|
||||
|
||||
// First child may be a text node, followed by the created HTML
|
||||
var child = div.firstChild;
|
||||
if (child != null && child.nodeType == 3)
|
||||
child = child.nextSibling;
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Make a copy of a HTML element, making it visible and clearing its ID to ensure it's not a duplicate
|
||||
//
|
||||
DOM.Node.Clone = function(name)
|
||||
{
|
||||
// Get the template element and clone it, making sure it's renderable
|
||||
var node = DOM.Node.Get(name);
|
||||
node = node.cloneNode(true);
|
||||
node.id = null;
|
||||
node.style.display = "block";
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Append an arbitrary block of HTML to an existing node
|
||||
//
|
||||
DOM.Node.AppendHTML = function(node, html)
|
||||
{
|
||||
var child = DOM.Node.CreateHTML(html);
|
||||
node.appendChild(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Append a div that clears the float style
|
||||
//
|
||||
DOM.Node.AppendClearFloat = function(node)
|
||||
{
|
||||
var div = document.createElement("div");
|
||||
div.style.clear = "both";
|
||||
node.appendChild(div);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Check to see that the object passed in is an instance of a DOM node
|
||||
//
|
||||
DOM.Node.IsNode = function(object)
|
||||
{
|
||||
return object instanceof Element;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Create an "iframe shim" so that elements within it render over a Java Applet
|
||||
// http://web.archive.org/web/20110707212850/http://www.oratransplant.nl/2007/10/26/using-iframe-shim-to-partly-cover-a-java-applet/
|
||||
//
|
||||
DOM.Node.CreateShim = function(parent)
|
||||
{
|
||||
var shimmer = document.createElement("iframe");
|
||||
|
||||
// Position the shimmer so that it's the same location/size as its parent
|
||||
shimmer.style.position = "fixed";
|
||||
shimmer.style.left = parent.style.left;
|
||||
shimmer.style.top = parent.style.top;
|
||||
shimmer.style.width = parent.offsetWidth;
|
||||
shimmer.style.height = parent.offsetHeight;
|
||||
|
||||
// We want the shimmer to be one level below its contents
|
||||
shimmer.style.zIndex = parent.style.zIndex - 1;
|
||||
|
||||
// Ensure its empty
|
||||
shimmer.setAttribute("frameborder", "0");
|
||||
shimmer.setAttribute("src", "");
|
||||
|
||||
// Add to the document and the parent
|
||||
document.body.appendChild(shimmer);
|
||||
parent.Shimmer = shimmer;
|
||||
return shimmer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// =====================================================================================================================
|
||||
// ----- EVENT HANDLING EXTENSIONS -------------------------------------------------------------------------------------
|
||||
// =====================================================================================================================
|
||||
//
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Retrieves the event from the first parameter passed into an HTML event
|
||||
//
|
||||
DOM.Event.Get = function(evt)
|
||||
{
|
||||
// Internet explorer doesn't pass the event
|
||||
return window.event || evt;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Retrieves the element that triggered an event from the event object
|
||||
//
|
||||
DOM.Event.GetNode = function(evt)
|
||||
{
|
||||
evt = DOM.Event.Get(evt);
|
||||
|
||||
// Get the target element
|
||||
var element;
|
||||
if (evt.target)
|
||||
element = evt.target;
|
||||
else if (e.srcElement)
|
||||
element = evt.srcElement;
|
||||
|
||||
// Default Safari bug
|
||||
if (element.nodeType == 3)
|
||||
element = element.parentNode;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Stop default action for an event
|
||||
//
|
||||
DOM.Event.StopDefaultAction = function(evt)
|
||||
{
|
||||
if (evt && evt.preventDefault)
|
||||
evt.preventDefault();
|
||||
else if (window.event && window.event.returnValue)
|
||||
window.event.returnValue = false;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Stops events bubbling up to parent event handlers
|
||||
//
|
||||
DOM.Event.StopPropagation = function(evt)
|
||||
{
|
||||
evt = DOM.Event.Get(evt);
|
||||
if (evt)
|
||||
{
|
||||
evt.cancelBubble = true;
|
||||
if (evt.stopPropagation)
|
||||
evt.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Stop both event default action and propagation
|
||||
//
|
||||
DOM.Event.StopAll = function(evt)
|
||||
{
|
||||
DOM.Event.StopDefaultAction(evt);
|
||||
DOM.Event.StopPropagation(evt);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Adds an event handler to an event
|
||||
//
|
||||
DOM.Event.AddHandler = function(obj, evt, func)
|
||||
{
|
||||
if (obj)
|
||||
{
|
||||
if (obj.addEventListener)
|
||||
obj.addEventListener(evt, func, false);
|
||||
else if (obj.attachEvent)
|
||||
obj.attachEvent("on" + evt, func);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Removes an event handler from an event
|
||||
//
|
||||
DOM.Event.RemoveHandler = function(obj, evt, func)
|
||||
{
|
||||
if (obj)
|
||||
{
|
||||
if (obj.removeEventListener)
|
||||
obj.removeEventListener(evt, func, false);
|
||||
else if (obj.detachEvent)
|
||||
obj.detachEvent("on" + evt, func);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Get the position of the mouse cursor, page relative
|
||||
//
|
||||
DOM.Event.GetMousePosition = function(evt)
|
||||
{
|
||||
evt = DOM.Event.Get(evt);
|
||||
|
||||
var px = 0;
|
||||
var py = 0;
|
||||
if (evt.pageX || evt.pageY)
|
||||
{
|
||||
px = evt.pageX;
|
||||
py = evt.pageY;
|
||||
}
|
||||
else if (evt.clientX || evt.clientY)
|
||||
{
|
||||
px = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
|
||||
py = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
|
||||
}
|
||||
|
||||
return [px, py];
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// =====================================================================================================================
|
||||
// ----- JAVA APPLET EXTENSIONS ----------------------------------------------------------------------------------------
|
||||
// =====================================================================================================================
|
||||
//
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Create an applet element for loading a Java applet, attaching it to the specified node
|
||||
//
|
||||
DOM.Applet.Load = function(dest_id, id, code, archive)
|
||||
{
|
||||
// Lookup the applet destination
|
||||
var dest = DOM.Node.Get(dest_id);
|
||||
if (!dest)
|
||||
return;
|
||||
|
||||
// Construct the applet element and add it to the destination
|
||||
Debug.Log("Injecting applet DOM code");
|
||||
var applet = "<applet id='" + id + "' code='" + code + "' archive='" + archive + "'";
|
||||
applet += " width='" + dest.offsetWidth + "' height='" + dest.offsetHeight + "'>";
|
||||
applet += "</applet>";
|
||||
dest.innerHTML = applet;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Moves and resizes a named applet so that it fits in the destination div element.
|
||||
// The applet must be contained by a div element itself. This container div is moved along
|
||||
// with the applet.
|
||||
//
|
||||
DOM.Applet.Move = function(dest_div, applet, z_index, hide)
|
||||
{
|
||||
if (!applet || !dest_div)
|
||||
return;
|
||||
|
||||
// Before modifying any location information, hide the applet so that it doesn't render over
|
||||
// any newly visible elements that appear while the location information is being modified.
|
||||
if (hide)
|
||||
applet.style.visibility = "hidden";
|
||||
|
||||
// Get its view rect
|
||||
var pos = DOM.Node.GetPosition(dest_div);
|
||||
var w = dest_div.offsetWidth;
|
||||
var h = dest_div.offsetHeight;
|
||||
|
||||
// It needs to be embedded in a <div> for correct scale/position adjustment
|
||||
var container = applet.parentNode;
|
||||
if (!container || container.localName != "div")
|
||||
{
|
||||
Debug.Log("ERROR: Couldn't find source applet's div container");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reposition and resize the containing div element
|
||||
container.style.left = pos[0];
|
||||
container.style.top = pos[1];
|
||||
container.style.width = w;
|
||||
container.style.height = h;
|
||||
container.style.zIndex = z_index;
|
||||
|
||||
// Resize the applet itself
|
||||
applet.style.width = w;
|
||||
applet.style.height = h;
|
||||
|
||||
// Everything modified, safe to show
|
||||
applet.style.visibility = "visible";
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
|
||||
namespace("Keyboard")
|
||||
|
||||
|
||||
// =====================================================================================================================
|
||||
// Key codes copied from closure-library
|
||||
// https://code.google.com/p/closure-library/source/browse/closure/goog/events/keycodes.js
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Keyboard.Codes = {
|
||||
WIN_KEY_FF_LINUX : 0,
|
||||
MAC_ENTER : 3,
|
||||
BACKSPACE : 8,
|
||||
TAB : 9,
|
||||
NUM_CENTER : 12, // NUMLOCK on FF/Safari Mac
|
||||
ENTER : 13,
|
||||
SHIFT : 16,
|
||||
CTRL : 17,
|
||||
ALT : 18,
|
||||
PAUSE : 19,
|
||||
CAPS_LOCK : 20,
|
||||
ESC : 27,
|
||||
SPACE : 32,
|
||||
PAGE_UP : 33, // also NUM_NORTH_EAST
|
||||
PAGE_DOWN : 34, // also NUM_SOUTH_EAST
|
||||
END : 35, // also NUM_SOUTH_WEST
|
||||
HOME : 36, // also NUM_NORTH_WEST
|
||||
LEFT : 37, // also NUM_WEST
|
||||
UP : 38, // also NUM_NORTH
|
||||
RIGHT : 39, // also NUM_EAST
|
||||
DOWN : 40, // also NUM_SOUTH
|
||||
PRINT_SCREEN : 44,
|
||||
INSERT : 45, // also NUM_INSERT
|
||||
DELETE : 46, // also NUM_DELETE
|
||||
ZERO : 48,
|
||||
ONE : 49,
|
||||
TWO : 50,
|
||||
THREE : 51,
|
||||
FOUR : 52,
|
||||
FIVE : 53,
|
||||
SIX : 54,
|
||||
SEVEN : 55,
|
||||
EIGHT : 56,
|
||||
NINE : 57,
|
||||
FF_SEMICOLON : 59, // Firefox (Gecko) fires this for semicolon instead of 186
|
||||
FF_EQUALS : 61, // Firefox (Gecko) fires this for equals instead of 187
|
||||
FF_DASH : 173, // Firefox (Gecko) fires this for dash instead of 189
|
||||
QUESTION_MARK : 63, // needs localization
|
||||
A : 65,
|
||||
B : 66,
|
||||
C : 67,
|
||||
D : 68,
|
||||
E : 69,
|
||||
F : 70,
|
||||
G : 71,
|
||||
H : 72,
|
||||
I : 73,
|
||||
J : 74,
|
||||
K : 75,
|
||||
L : 76,
|
||||
M : 77,
|
||||
N : 78,
|
||||
O : 79,
|
||||
P : 80,
|
||||
Q : 81,
|
||||
R : 82,
|
||||
S : 83,
|
||||
T : 84,
|
||||
U : 85,
|
||||
V : 86,
|
||||
W : 87,
|
||||
X : 88,
|
||||
Y : 89,
|
||||
Z : 90,
|
||||
META : 91, // WIN_KEY_LEFT
|
||||
WIN_KEY_RIGHT : 92,
|
||||
CONTEXT_MENU : 93,
|
||||
NUM_ZERO : 96,
|
||||
NUM_ONE : 97,
|
||||
NUM_TWO : 98,
|
||||
NUM_THREE : 99,
|
||||
NUM_FOUR : 100,
|
||||
NUM_FIVE : 101,
|
||||
NUM_SIX : 102,
|
||||
NUM_SEVEN : 103,
|
||||
NUM_EIGHT : 104,
|
||||
NUM_NINE : 105,
|
||||
NUM_MULTIPLY : 106,
|
||||
NUM_PLUS : 107,
|
||||
NUM_MINUS : 109,
|
||||
NUM_PERIOD : 110,
|
||||
NUM_DIVISION : 111,
|
||||
F1 : 112,
|
||||
F2 : 113,
|
||||
F3 : 114,
|
||||
F4 : 115,
|
||||
F5 : 116,
|
||||
F6 : 117,
|
||||
F7 : 118,
|
||||
F8 : 119,
|
||||
F9 : 120,
|
||||
F10 : 121,
|
||||
F11 : 122,
|
||||
F12 : 123,
|
||||
NUMLOCK : 144,
|
||||
SCROLL_LOCK : 145,
|
||||
|
||||
// OS-specific media keys like volume controls and browser controls.
|
||||
FIRST_MEDIA_KEY : 166,
|
||||
LAST_MEDIA_KEY : 183,
|
||||
|
||||
SEMICOLON : 186, // needs localization
|
||||
DASH : 189, // needs localization
|
||||
EQUALS : 187, // needs localization
|
||||
COMMA : 188, // needs localization
|
||||
PERIOD : 190, // needs localization
|
||||
SLASH : 191, // needs localization
|
||||
APOSTROPHE : 192, // needs localization
|
||||
TILDE : 192, // needs localization
|
||||
SINGLE_QUOTE : 222, // needs localization
|
||||
OPEN_SQUARE_BRACKET : 219, // needs localization
|
||||
BACKSLASH : 220, // needs localization
|
||||
CLOSE_SQUARE_BRACKET: 221, // needs localization
|
||||
WIN_KEY : 224,
|
||||
MAC_FF_META : 224, // Firefox (Gecko) fires this for the meta key instead of 91
|
||||
MAC_WK_CMD_LEFT : 91, // WebKit Left Command key fired, same as META
|
||||
MAC_WK_CMD_RIGHT : 93, // WebKit Right Command key fired, different from META
|
||||
WIN_IME : 229,
|
||||
|
||||
// We've seen users whose machines fire this keycode at regular one
|
||||
// second intervals. The common thread among these users is that
|
||||
// they're all using Dell Inspiron laptops, so we suspect that this
|
||||
// indicates a hardware/bios problem.
|
||||
// http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx
|
||||
PHANTOM : 255
|
||||
};
|
||||
// =====================================================================================================================
|
|
@ -1,40 +0,0 @@
|
|||
|
||||
namespace("LocalStore");
|
||||
|
||||
|
||||
LocalStore.Set = function(class_name, class_id, variable_id, data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (typeof(Storage) != "undefined")
|
||||
{
|
||||
var name = class_name + "_" + class_id + "_" + variable_id;
|
||||
localStorage[name] = JSON.stringify(data);
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log("Local Storage Set Error: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LocalStore.Get = function(class_name, class_id, variable_id, default_data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (typeof(Storage) != "undefined")
|
||||
{
|
||||
var name = class_name + "_" + class_id + "_" + variable_id;
|
||||
var data = localStorage[name]
|
||||
if (data)
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log("Local Storage Get Error: " + e.message);
|
||||
}
|
||||
|
||||
return default_data;
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
|
||||
namespace("Mouse");
|
||||
|
||||
|
||||
Mouse.State =(function()
|
||||
{
|
||||
function State(event)
|
||||
{
|
||||
// Get button press states
|
||||
if (typeof event.buttons != "undefined")
|
||||
{
|
||||
// Firefox
|
||||
this.Left = (event.buttons & 1) != 0;
|
||||
this.Right = (event.buttons & 2) != 0;
|
||||
this.Middle = (event.buttons & 4) != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chrome
|
||||
this.Left = (event.button == 0);
|
||||
this.Middle = (event.button == 1);
|
||||
this.Right = (event.button == 2);
|
||||
}
|
||||
|
||||
// Get page-relative mouse position
|
||||
this.Position = DOM.Event.GetMousePosition(event);
|
||||
|
||||
// Get wheel delta
|
||||
var delta = 0;
|
||||
if (event.wheelDelta)
|
||||
delta = event.wheelDelta / 120; // IE/Opera
|
||||
else if (event.detail)
|
||||
delta = -event.detail / 3; // Mozilla
|
||||
this.WheelDelta = delta;
|
||||
|
||||
// Get the mouse position delta
|
||||
// Requires Pointer Lock API support
|
||||
this.PositionDelta = [
|
||||
event.movementX || event.mozMovementX || event.webkitMovementX || 0,
|
||||
event.movementY || event.mozMovementY || event.webkitMovementY || 0
|
||||
];
|
||||
}
|
||||
|
||||
return State;
|
||||
})();
|
||||
|
||||
|
||||
//
|
||||
// Basic Pointer Lock API support
|
||||
// https://developer.mozilla.org/en-US/docs/WebAPI/Pointer_Lock
|
||||
// http://www.chromium.org/developers/design-documents/mouse-lock
|
||||
//
|
||||
// Note that API has not been standardised yet so browsers can implement functions with prefixes
|
||||
//
|
||||
|
||||
|
||||
Mouse.PointerLockSupported = function()
|
||||
{
|
||||
return 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
|
||||
}
|
||||
|
||||
|
||||
Mouse.RequestPointerLock = function(element)
|
||||
{
|
||||
element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;
|
||||
if (element.requestPointerLock)
|
||||
element.requestPointerLock();
|
||||
}
|
||||
|
||||
|
||||
Mouse.ExitPointerLock = function()
|
||||
{
|
||||
document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock;
|
||||
if (document.exitPointerLock)
|
||||
document.exitPointerLock();
|
||||
}
|
||||
|
||||
|
||||
// Can use this element to detect whether pointer lock is enabled (returns non-null)
|
||||
Mouse.PointerLockElement = function()
|
||||
{
|
||||
return document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement;
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
|
||||
namespace("Hash");
|
||||
|
||||
/**
|
||||
* JS Implementation of MurmurHash3 (r136) (as of May 20, 2011)
|
||||
*
|
||||
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
|
||||
* @see http://github.com/garycourt/murmurhash-js
|
||||
* @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
|
||||
* @see http://sites.google.com/site/murmurhash/
|
||||
*
|
||||
* @param {string} key ASCII only
|
||||
* @param {number} seed Positive integer only
|
||||
* @return {number} 32-bit positive integer hash
|
||||
*/
|
||||
|
||||
Hash.Murmur3 = function(key, seed)
|
||||
{
|
||||
var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i;
|
||||
|
||||
remainder = key.length & 3; // key.length % 4
|
||||
bytes = key.length - remainder;
|
||||
h1 = seed;
|
||||
c1 = 0xcc9e2d51;
|
||||
c2 = 0x1b873593;
|
||||
i = 0;
|
||||
|
||||
while (i < bytes) {
|
||||
k1 =
|
||||
((key.charCodeAt(i) & 0xff)) |
|
||||
((key.charCodeAt(++i) & 0xff) << 8) |
|
||||
((key.charCodeAt(++i) & 0xff) << 16) |
|
||||
((key.charCodeAt(++i) & 0xff) << 24);
|
||||
++i;
|
||||
|
||||
k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff;
|
||||
k1 = (k1 << 15) | (k1 >>> 17);
|
||||
k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff;
|
||||
|
||||
h1 ^= k1;
|
||||
h1 = (h1 << 13) | (h1 >>> 19);
|
||||
h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff;
|
||||
h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16));
|
||||
}
|
||||
|
||||
k1 = 0;
|
||||
|
||||
switch (remainder) {
|
||||
case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
|
||||
case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
|
||||
case 1: k1 ^= (key.charCodeAt(i) & 0xff);
|
||||
|
||||
k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
|
||||
k1 = (k1 << 15) | (k1 >>> 17);
|
||||
k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
|
||||
h1 ^= k1;
|
||||
}
|
||||
|
||||
h1 ^= key.length;
|
||||
|
||||
h1 ^= h1 >>> 16;
|
||||
h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
|
||||
h1 ^= h1 >>> 13;
|
||||
h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff;
|
||||
h1 ^= h1 >>> 16;
|
||||
|
||||
return h1 >>> 0;
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
|
||||
namespace("WM");
|
||||
|
||||
|
||||
WM.Button = (function()
|
||||
{
|
||||
var template_html = "<div class='Button notextsel'></div>";
|
||||
|
||||
|
||||
function Button(text, x, y, opts)
|
||||
{
|
||||
this.OnClick = null;
|
||||
this.Toggle = opts && opts.toggle;
|
||||
|
||||
this.Node = DOM.Node.CreateHTML(template_html);
|
||||
|
||||
// Set node dimensions
|
||||
this.SetPosition(x, y);
|
||||
if (opts && opts.w && opts.h)
|
||||
this.SetSize(opts.w, opts.h);
|
||||
|
||||
// Override the default class name
|
||||
if (opts && opts.class)
|
||||
this.Node.className = opts.class;
|
||||
|
||||
this.SetText(text);
|
||||
|
||||
// Create the mouse press event handlers
|
||||
DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this));
|
||||
this.OnMouseOutDelegate = Bind(OnMouseUp, this, false);
|
||||
this.OnMouseUpDelegate = Bind(OnMouseUp, this, true);
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.SetPosition = function(x, y)
|
||||
{
|
||||
this.Position = [ x, y ];
|
||||
DOM.Node.SetPosition(this.Node, this.Position);
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.SetSize = function(w, h)
|
||||
{
|
||||
this.Size = [ w, h ];
|
||||
DOM.Node.SetSize(this.Node, this.Size);
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.SetText = function(text)
|
||||
{
|
||||
this.Node.innerHTML = text;
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.SetOnClick = function(on_click)
|
||||
{
|
||||
this.OnClick = on_click;
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.SetState = function(pressed)
|
||||
{
|
||||
if (pressed)
|
||||
DOM.Node.AddClass(this.Node, "ButtonHeld");
|
||||
else
|
||||
DOM.Node.RemoveClass(this.Node, "ButtonHeld");
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.ToggleState = function()
|
||||
{
|
||||
if (DOM.Node.HasClass(this.Node, "ButtonHeld"))
|
||||
this.SetState(false);
|
||||
else
|
||||
this.SetState(true);
|
||||
}
|
||||
|
||||
|
||||
Button.prototype.IsPressed = function()
|
||||
{
|
||||
return DOM.Node.HasClass(this.Node, "ButtonHeld");
|
||||
}
|
||||
|
||||
|
||||
function OnMouseDown(self, evt)
|
||||
{
|
||||
// Decide how to set the button state
|
||||
if (self.Toggle)
|
||||
self.ToggleState();
|
||||
else
|
||||
self.SetState(true);
|
||||
|
||||
// Activate release handlers
|
||||
DOM.Event.AddHandler(self.Node, "mouseout", self.OnMouseOutDelegate);
|
||||
DOM.Event.AddHandler(self.Node, "mouseup", self.OnMouseUpDelegate);
|
||||
|
||||
DOM.Event.StopAll(evt);
|
||||
}
|
||||
|
||||
|
||||
function OnMouseUp(self, confirm, evt)
|
||||
{
|
||||
if (confirm)
|
||||
{
|
||||
// Only release for non-toggles
|
||||
if (!self.Toggle)
|
||||
self.SetState(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Decide how to set the button state
|
||||
if (self.Toggle)
|
||||
self.ToggleState();
|
||||
else
|
||||
self.SetState(false);
|
||||
}
|
||||
|
||||
// Remove release handlers
|
||||
DOM.Event.RemoveHandler(self.Node, "mouseout", self.OnMouseOutDelegate);
|
||||
DOM.Event.RemoveHandler(self.Node, "mouseup", self.OnMouseUpDelegate);
|
||||
|
||||
// Call the click handler if this is a button press
|
||||
if (confirm && self.OnClick)
|
||||
self.OnClick(self);
|
||||
|
||||
DOM.Event.StopAll(evt);
|
||||
}
|
||||
|
||||
|
||||
return Button;
|
||||
})();
|
|
@ -1,237 +0,0 @@
|
|||
|
||||
namespace("WM");
|
||||
|
||||
|
||||
WM.ComboBoxPopup = (function()
|
||||
{
|
||||
var body_template_html = "<div class='ComboBoxPopup'></div>";
|
||||
|
||||
var item_template_html = " \
|
||||
<div class='ComboBoxPopupItem notextsel'> \
|
||||
<div class='ComboBoxPopupItemText'></div> \
|
||||
<div class='ComboBoxPopupItemIcon'><img src='BrowserLibImages/tick.gif'></div> \
|
||||
<div style='clear:both'></div> \
|
||||
</div>";
|
||||
|
||||
|
||||
function ComboBoxPopup(combo_box)
|
||||
{
|
||||
this.ComboBox = combo_box;
|
||||
this.ParentNode = combo_box.Node;
|
||||
this.ValueNodes = [ ];
|
||||
|
||||
// Create the template node
|
||||
this.Node = DOM.Node.CreateHTML(body_template_html);
|
||||
|
||||
DOM.Event.AddHandler(this.Node, "mousedown", Bind(SelectItem, this));
|
||||
this.CancelDelegate = Bind(this, "Cancel");
|
||||
}
|
||||
|
||||
|
||||
ComboBoxPopup.prototype.SetValues = function(values)
|
||||
{
|
||||
// Clear existing values
|
||||
this.Node.innerHTML = "";
|
||||
|
||||
// Generate HTML nodes for each value
|
||||
this.ValueNodes = [ ];
|
||||
for (var i in values)
|
||||
{
|
||||
var item_node = DOM.Node.CreateHTML(item_template_html);
|
||||
var text_node = DOM.Node.FindWithClass(item_node, "ComboBoxPopupItemText");
|
||||
|
||||
item_node.Value = values[i];
|
||||
text_node.innerHTML = values[i];
|
||||
|
||||
this.Node.appendChild(item_node);
|
||||
this.ValueNodes.push(item_node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ComboBoxPopup.prototype.Show = function(selection_index)
|
||||
{
|
||||
// Initially match the position of the parent node
|
||||
var pos = DOM.Node.GetPosition(this.ParentNode);
|
||||
DOM.Node.SetPosition(this.Node, pos);
|
||||
|
||||
// Take the width/z-index from the parent node
|
||||
this.Node.style.width = this.ParentNode.offsetWidth;
|
||||
this.Node.style.zIndex = this.ParentNode.style.zIndex + 1;
|
||||
|
||||
// Setup event handlers
|
||||
DOM.Event.AddHandler(document.body, "mousedown", this.CancelDelegate);
|
||||
|
||||
// Show the popup so that the HTML layout engine kicks in before
|
||||
// the layout info is used below
|
||||
this.ParentNode.appendChild(this.Node);
|
||||
|
||||
// Show/hide the tick image based on which node is selected
|
||||
for (var i in this.ValueNodes)
|
||||
{
|
||||
var node = this.ValueNodes[i];
|
||||
var icon_node = DOM.Node.FindWithClass(node, "ComboBoxPopupItemIcon");
|
||||
|
||||
if (i == selection_index)
|
||||
{
|
||||
icon_node.style.display = "block";
|
||||
|
||||
// Also, shift the popup up so that the mouse is over the selected item and is highlighted
|
||||
var item_pos = DOM.Node.GetPosition(this.ValueNodes[selection_index]);
|
||||
var diff_pos = [ item_pos[0] - pos[0], item_pos[1] - pos[1] ];
|
||||
pos = [ pos[0] - diff_pos[0], pos[1] - diff_pos[1] ];
|
||||
}
|
||||
else
|
||||
{
|
||||
icon_node.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
DOM.Node.SetPosition(this.Node, pos);
|
||||
}
|
||||
|
||||
|
||||
ComboBoxPopup.prototype.Hide = function()
|
||||
{
|
||||
DOM.Event.RemoveHandler(document.body, "mousedown", this.CancelDelegate);
|
||||
this.ParentNode.removeChild(this.Node);
|
||||
}
|
||||
|
||||
|
||||
function SelectItem(self, evt)
|
||||
{
|
||||
// Search for which item node is being clicked on
|
||||
var node = DOM.Event.GetNode(evt);
|
||||
for (var i in self.ValueNodes)
|
||||
{
|
||||
var value_node = self.ValueNodes[i];
|
||||
if (DOM.Node.Contains(node, value_node))
|
||||
{
|
||||
// Set the value on the combo box
|
||||
self.ComboBox.SetValue(value_node.Value);
|
||||
self.Hide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Cancel(self, evt)
|
||||
{
|
||||
// Don't cancel if the mouse up is anywhere on the popup or combo box
|
||||
var node = DOM.Event.GetNode(evt);
|
||||
if (!DOM.Node.Contains(node, self.Node) &&
|
||||
!DOM.Node.Contains(node, self.ParentNode))
|
||||
{
|
||||
self.Hide();
|
||||
}
|
||||
|
||||
|
||||
DOM.Event.StopAll(evt);
|
||||
}
|
||||
|
||||
|
||||
return ComboBoxPopup;
|
||||
})();
|
||||
|
||||
|
||||
WM.ComboBox = (function()
|
||||
{
|
||||
var template_html = " \
|
||||
<div class='ComboBox'> \
|
||||
<div class='ComboBoxText notextsel'></div> \
|
||||
<div class='ComboBoxIcon'><img src='BrowserLibImages/up_down.gif'></div> \
|
||||
<div style='clear:both'></div> \
|
||||
</div>";
|
||||
|
||||
|
||||
function ComboBox()
|
||||
{
|
||||
this.OnChange = null;
|
||||
|
||||
// Create the template node and locate key nodes
|
||||
this.Node = DOM.Node.CreateHTML(template_html);
|
||||
this.TextNode = DOM.Node.FindWithClass(this.Node, "ComboBoxText");
|
||||
|
||||
// Create a reusable popup
|
||||
this.Popup = new WM.ComboBoxPopup(this);
|
||||
|
||||
// Set an empty set of values
|
||||
this.SetValues([]);
|
||||
this.SetValue("<empty>");
|
||||
|
||||
// Create the mouse press event handlers
|
||||
DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this));
|
||||
this.OnMouseOutDelegate = Bind(OnMouseUp, this, false);
|
||||
this.OnMouseUpDelegate = Bind(OnMouseUp, this, true);
|
||||
}
|
||||
|
||||
|
||||
ComboBox.prototype.SetOnChange = function(on_change)
|
||||
{
|
||||
this.OnChange = on_change;
|
||||
}
|
||||
|
||||
|
||||
ComboBox.prototype.SetValues = function(values)
|
||||
{
|
||||
this.Values = values;
|
||||
this.Popup.SetValues(values);
|
||||
}
|
||||
|
||||
|
||||
ComboBox.prototype.SetValue = function(value)
|
||||
{
|
||||
// Set the value and its HTML rep
|
||||
var old_value = this.Value;
|
||||
this.Value = value;
|
||||
this.TextNode.innerHTML = value;
|
||||
|
||||
// Call change handler
|
||||
if (this.OnChange)
|
||||
this.OnChange(value, old_value);
|
||||
}
|
||||
|
||||
|
||||
ComboBox.prototype.GetValue = function()
|
||||
{
|
||||
return this.Value;
|
||||
}
|
||||
|
||||
|
||||
function OnMouseDown(self, evt)
|
||||
{
|
||||
// If this check isn't made, the click will trigger from the popup, too
|
||||
var node = DOM.Event.GetNode(evt);
|
||||
if (DOM.Node.Contains(node, self.Node))
|
||||
{
|
||||
// Add the depression class and activate release handlers
|
||||
DOM.Node.AddClass(self.Node, "ComboBoxPressed");
|
||||
DOM.Event.AddHandler(self.Node, "mouseout", self.OnMouseOutDelegate);
|
||||
DOM.Event.AddHandler(self.Node, "mouseup", self.OnMouseUpDelegate);
|
||||
|
||||
DOM.Event.StopAll(evt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function OnMouseUp(self, confirm, evt)
|
||||
{
|
||||
// Remove depression class and remove release handlers
|
||||
DOM.Node.RemoveClass(self.Node, "ComboBoxPressed");
|
||||
DOM.Event.RemoveHandler(self.Node, "mouseout", self.OnMouseOutDelegate);
|
||||
DOM.Event.RemoveHandler(self.Node, "mouseup", self.OnMouseUpDelegate);
|
||||
|
||||
// If this is a confirmed press and there are some values in the list, show the popup
|
||||
if (confirm && self.Values.length > 0)
|
||||
{
|
||||
var selection_index = self.Values.indexOf(self.Value);
|
||||
self.Popup.Show(selection_index);
|
||||
}
|
||||
|
||||
DOM.Event.StopAll(evt);
|
||||
}
|
||||
|
||||
|
||||
return ComboBox;
|
||||
})();
|
|
@ -1,34 +0,0 @@
|
|||
|
||||
namespace("WM");
|
||||
|
||||
|
||||
WM.Container = (function()
|
||||
{
|
||||
var template_html = "<div class='Container'></div>";
|
||||
|
||||
|
||||
function Container(x, y, w, h)
|
||||
{
|
||||
// Create a simple container node
|
||||
this.Node = DOM.Node.CreateHTML(template_html);
|
||||
this.SetPosition(x, y);
|
||||
this.SetSize(w, h);
|
||||
}
|
||||
|
||||
|
||||
Container.prototype.SetPosition = function(x, y)
|
||||
{
|
||||
this.Position = [ x, y ];
|
||||
DOM.Node.SetPosition(this.Node, this.Position);
|
||||
}
|
||||
|
||||
|
||||
Container.prototype.SetSize = function(w, h)
|
||||
{
|
||||
this.Size = [ w, h ];
|
||||
DOM.Node.SetSize(this.Node, this.Size);
|
||||
}
|
||||
|
||||
|
||||
return Container;
|
||||
})();
|
|
@ -1,119 +0,0 @@
|
|||
|
||||
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;
|
||||
})();
|
|
@ -1,248 +0,0 @@
|
|||
|
||||
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;
|
||||
})();
|
|
@ -1,31 +0,0 @@
|
|||
|
||||
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;
|
||||
})();
|
|
@ -1,352 +0,0 @@
|
|||
|
||||
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;
|
||||
})();
|
|
@ -1,109 +0,0 @@
|
|||
|
||||
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;
|
||||
})();
|
|
@ -1,295 +0,0 @@
|
|||
|
||||
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()
|
||||
{
|
||||
// Remove node
|
||||
this.ParentNode.removeChild(this.Node);
|
||||
this.Visible = false;
|
||||
}
|
||||
|
||||
|
||||
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.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;
|
||||
})();
|
|
@ -1,54 +0,0 @@
|
|||
|
||||
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.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;
|
||||
|
||||
})();
|
|
@ -1,652 +0,0 @@
|
|||
|
||||
|
||||
.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;
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
|
||||
<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/DataViewReader.js"></script>
|
||||
<script type="text/javascript" src="Code/Console.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/TimelineWindow.js"></script>
|
||||
<script type="text/javascript" src="Code/ThreadFrame.js"></script>
|
||||
<script type="text/javascript" src="Code/Remotery.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var remotery = new Remotery();
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,94 +0,0 @@
|
|||
|
||||
<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="../typescript/remotery.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div style="width: 100%; height: 10px;"></div>
|
||||
<div style="float: left; width: 1020px; height: 10px;"></div>
|
||||
<textarea id="website" rows="20" cols="24">Window Layout</textarea>
|
||||
<div style="clear: both;"/>
|
||||
<div style="width: 100%; height: 5px;"></div>
|
||||
<div style="float: left; width: 1020px; height: 10px;"></div>
|
||||
<button id="load-button" data-copytarget="#website">Load</button>
|
||||
<button id="save-button" data-copytarget="#website">Save</button>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
//var remotery = new Remotery();
|
||||
var Container = TestAll();
|
||||
|
||||
// event handler
|
||||
function copy(e)
|
||||
{
|
||||
// find target element
|
||||
var
|
||||
t = e.target,
|
||||
c = t.dataset.copytarget,
|
||||
inp = (c ? document.querySelector(c) : null);
|
||||
|
||||
// Save container and write to text area
|
||||
inp.value = WM.SaveContainer(Container);
|
||||
|
||||
// Commit to localstore
|
||||
if (typeof(localStorage) !== "undefined")
|
||||
localStorage.layout = inp.value;
|
||||
|
||||
// is element selectable?
|
||||
if (inp && inp.select)
|
||||
{
|
||||
// select text
|
||||
inp.select();
|
||||
|
||||
try
|
||||
{
|
||||
// copy text
|
||||
document.execCommand('copy');
|
||||
inp.blur();
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
alert('please press Ctrl/Cmd+C to copy');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function load(e)
|
||||
{
|
||||
// find target element
|
||||
var
|
||||
t = e.target,
|
||||
c = t.dataset.copytarget,
|
||||
inp = (c ? document.querySelector(c) : null);
|
||||
|
||||
WM.LoadContainer(Container, inp.value);
|
||||
}
|
||||
|
||||
// click events
|
||||
$("#save-button").Element.addEventListener('click', copy, true);
|
||||
$("#load-button").Element.addEventListener('click', load, true);
|
||||
|
||||
// Load existing layout from user's local store
|
||||
if (typeof(Storage) !== "undefined" && localStorage.layout)
|
||||
{
|
||||
$("#website").Element.value = localStorage.layout;
|
||||
WM.LoadContainer(Container, localStorage.layout);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
Subproject commit cbcf393332ceaa5ea8abe6b2dbb761b1df822010
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
#include "../world/Chunk.hpp"
|
||||
#include "../world/materials.hpp"
|
||||
#include <Remotery.h> // NOLINT
|
||||
#include <Tracy.hpp> // NOLINT
|
||||
#include <common/TracySystem.hpp> // NOLINT
|
||||
#include <imgui.h> // NOLINT
|
||||
#include <toml.h>
|
||||
#include "dualmc.h"
|
||||
|
@ -12,14 +13,18 @@ namespace contouring {
|
|||
auto opt = toml::parse(str);
|
||||
iso = opt["iso"].value_or(iso);
|
||||
manifold = opt["manifold"].value_or(manifold);
|
||||
reordering = opt["reordering"].value_or(reordering);
|
||||
|
||||
for (size_t i = 1; i <= 2; i++) {
|
||||
workers.emplace_back([&] {
|
||||
#if TRACY_ENABLE
|
||||
tracy::SetThreadName("Contouring");
|
||||
#endif
|
||||
while (running) {
|
||||
std::pair<area_<chunk_pos>, surrounding::corners> ctx;
|
||||
loadQueue.wait();
|
||||
if (loadQueue.pop(ctx)) {
|
||||
rmt_ScopedCPUSample(ProcessContouring, 0);
|
||||
ZoneScopedN("ProcessContouring");
|
||||
buffer::ShortIndexed::Data data;
|
||||
render(ctx.second, data);
|
||||
loadedQueue.emplace(ctx.first, data);
|
||||
|
@ -44,13 +49,14 @@ namespace contouring {
|
|||
{"load_distance", loadDistance},
|
||||
{"keep_distance", keepDistance},
|
||||
{"iso", iso},
|
||||
{"manifold", manifold}
|
||||
{"manifold", manifold},
|
||||
{"reordering", reordering}
|
||||
});
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void FlatDualMC::enqueue(const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data) {
|
||||
rmt_ScopedCPUSample(EnqueueContouring, RMTSF_Aggregate);
|
||||
ZoneScopedN("EnqueueContouring");
|
||||
const auto dist2 = glm::length2(offset - pos.second);
|
||||
if (dist2 <= loadDistance * loadDistance) {
|
||||
surrounding::corners surrounding;
|
||||
|
@ -78,9 +84,9 @@ namespace contouring {
|
|||
|
||||
void FlatDualMC::update(const voxel_pos& pos, const world::area_map& areas) {
|
||||
std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data> out;
|
||||
reports.load.push(loadQueue.size());
|
||||
TracyPlot("CtLoad", static_cast<int64_t>(loadQueue.size()));
|
||||
//MAYBE: clear out of range loadQueue.trim(keepDistance * keepDistance)
|
||||
reports.loaded.push(loadedQueue.size());
|
||||
TracyPlot("CtLoaded", static_cast<int64_t>(loadedQueue.size()));
|
||||
while(loadedQueue.pop(out)) {
|
||||
const auto buffer = new buffer::ShortIndexed(GL_TRIANGLES, out.second);
|
||||
auto &bfs = buffers[out.first.first].second; //NOTE: buffer.first uninitialized (will be set in clear())
|
||||
|
@ -93,19 +99,17 @@ namespace contouring {
|
|||
bfs.emplace(out.first.second, buffer);
|
||||
}
|
||||
}
|
||||
size_t count = AbstractFlat::clear(pos, areas);
|
||||
reports.count.push(count);
|
||||
[[maybe_unused]]
|
||||
int64_t count = AbstractFlat::clear(pos, areas);
|
||||
TracyPlot("CtCount", count);
|
||||
}
|
||||
|
||||
void FlatDualMC::onGui() {
|
||||
ImGui::PlotHistogram("Count", reports.count.buffer.get(), reports.count.size, 0, std::to_string(reports.count.current()).c_str(), 0);
|
||||
ImGui::PlotHistogram("Loading", reports.load.buffer.get(), reports.load.size, 0, std::to_string(reports.load.current()).c_str(), 0);
|
||||
ImGui::PlotHistogram("Waiting", reports.loaded.buffer.get(), reports.loaded.size, 0, std::to_string(reports.loaded.current()).c_str(), 0);
|
||||
ImGui::Separator();
|
||||
AbstractFlat::onGui();
|
||||
ImGui::Separator();
|
||||
ImGui::SliderFloat("Iso", &iso, 0, 1);
|
||||
ImGui::Checkbox("Manifold", &manifold);
|
||||
ImGui::Checkbox("Reordering", &reordering);
|
||||
}
|
||||
|
||||
void FlatDualMC::render(const surrounding::corners &surrounding, buffer::ShortIndexed::Data &out) const {
|
||||
|
@ -140,14 +144,20 @@ namespace contouring {
|
|||
|
||||
out.indices.reserve(dmc_tris.size());
|
||||
for (const auto& t: dmc_tris) {
|
||||
out.indices.push_back(t.i0);
|
||||
out.indices.push_back(t.i1);
|
||||
out.indices.push_back(t.i2);
|
||||
|
||||
glm::vec3 edge1 = out.vertices[t.i1] - out.vertices[t.i0];
|
||||
glm::vec3 edge2 = out.vertices[t.i2] - out.vertices[t.i0];
|
||||
glm::vec3 normal = glm::normalize(glm::cross(edge1, edge2));
|
||||
|
||||
if(!reordering || glm::length2(edge1) > glm::length2(edge2)) {
|
||||
out.indices.push_back(t.i0);
|
||||
out.indices.push_back(t.i1);
|
||||
out.indices.push_back(t.i2);
|
||||
} else {
|
||||
out.indices.push_back(t.i2);
|
||||
out.indices.push_back(t.i0);
|
||||
out.indices.push_back(t.i1);
|
||||
}
|
||||
|
||||
out.normals[t.i0] += normal;
|
||||
out.normals[t.i1] += normal;
|
||||
out.normals[t.i2] += normal;
|
||||
|
|
|
@ -33,12 +33,6 @@ namespace contouring {
|
|||
safe_priority_queue_map<area_<chunk_pos>, surrounding::corners, int, area_hash> loadQueue;
|
||||
safe_queue<std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data>> loadedQueue;
|
||||
|
||||
struct report {
|
||||
report_buffer count;
|
||||
report_buffer load;
|
||||
report_buffer loaded;
|
||||
} reports;
|
||||
|
||||
bool running = true;
|
||||
std::vector<std::thread> workers;
|
||||
|
||||
|
@ -46,6 +40,7 @@ namespace contouring {
|
|||
|
||||
float iso = .1f;
|
||||
bool manifold = true;
|
||||
bool reordering = true;
|
||||
|
||||
void render(const surrounding::corners &surrounding, buffer::ShortIndexed::Data& out) const;
|
||||
};
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "boxing.hpp"
|
||||
#include "../world/Chunk.hpp"
|
||||
#include <Remotery.h> // NOLINT
|
||||
#include <imgui.h> // NOLINT
|
||||
|
||||
using namespace geometry;
|
||||
|
@ -14,11 +13,9 @@ namespace contouring {
|
|||
std::pair<area_<chunk_pos>, surrounding::faces> ctx;
|
||||
loadQueue.wait();
|
||||
if (loadQueue.pop(ctx)) {
|
||||
rmt_ScopedCPUSample(ProcessContouring, 0);
|
||||
std::vector<buffer::VertexData> vertices;
|
||||
render(ctx.second, vertices);
|
||||
{
|
||||
rmt_ScopedCPUSample(Index, 0);
|
||||
loadedQueue.emplace(ctx.first, vertices);
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +34,6 @@ namespace contouring {
|
|||
}
|
||||
|
||||
void FlatSurroundingBox::enqueue(const area_<chunk_pos> &pos, const chunk_pos& offset, const world::ChunkContainer &data) {
|
||||
rmt_ScopedCPUSample(EnqueueContouring, RMTSF_Aggregate);
|
||||
const auto dist2 = glm::length2(offset - pos.second);
|
||||
if (dist2 <= loadDistance * loadDistance) {
|
||||
surrounding::faces surrounding;
|
||||
|
@ -77,9 +73,7 @@ namespace contouring {
|
|||
|
||||
void FlatSurroundingBox::update(const voxel_pos& pos, const world::area_map& areas) {
|
||||
std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data> out;
|
||||
reports.load.push(loadQueue.size());
|
||||
//MAYBE: clear out of range loadQueue.trim(keepDistance * keepDistance)
|
||||
reports.loaded.push(loadedQueue.size());
|
||||
while(loadedQueue.pop(out)) {
|
||||
const auto buffer = new buffer::ShortIndexed(GL_TRIANGLES, out.second);
|
||||
auto& bfs = buffers[out.first.first].second; //NOTE: buffer.first uninitialized
|
||||
|
@ -92,16 +86,7 @@ namespace contouring {
|
|||
bfs.emplace(out.first.second, buffer);
|
||||
}
|
||||
}
|
||||
size_t count = AbstractFlat::clear(pos, areas);
|
||||
reports.count.push(count);
|
||||
}
|
||||
|
||||
void FlatSurroundingBox::onGui() {
|
||||
ImGui::PlotHistogram("Count", reports.count.buffer.get(), reports.count.size, 0, std::to_string(reports.count.current()).c_str(), 0);
|
||||
ImGui::PlotHistogram("Loading", reports.load.buffer.get(), reports.load.size, 0, std::to_string(reports.load.current()).c_str(), 0);
|
||||
ImGui::PlotHistogram("Waiting", reports.loaded.buffer.get(), reports.loaded.size, 0, std::to_string(reports.loaded.current()).c_str(), 0);
|
||||
ImGui::Separator();
|
||||
AbstractFlat::onGui();
|
||||
AbstractFlat::clear(pos, areas);
|
||||
}
|
||||
|
||||
bool FlatSurroundingBox::isTransparent(const surrounding::faces &surrounding, const std::pair<ushort, ushort> &idx) {
|
||||
|
|
|
@ -19,8 +19,6 @@ namespace contouring {
|
|||
|
||||
void update(const voxel_pos&, const world::area_map&) override;
|
||||
|
||||
void onGui() override;
|
||||
|
||||
/// Chunk data change
|
||||
void onUpdate(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &, geometry::Faces) override;
|
||||
/// Chunk existante ping
|
||||
|
@ -31,12 +29,6 @@ namespace contouring {
|
|||
safe_priority_queue_map<area_<chunk_pos>, surrounding::faces, int, area_hash> loadQueue;
|
||||
safe_queue<std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data>> loadedQueue;
|
||||
|
||||
struct report {
|
||||
report_buffer count;
|
||||
report_buffer load;
|
||||
report_buffer loaded;
|
||||
} reports;
|
||||
|
||||
bool running = true;
|
||||
std::vector<std::thread> workers;
|
||||
|
||||
|
|
|
@ -25,8 +25,4 @@ namespace data {
|
|||
return buffer[last];
|
||||
}
|
||||
};
|
||||
|
||||
struct report_buffer: circular_buffer<float> {
|
||||
report_buffer() : circular_buffer(256, 0) {}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <algorithm>
|
||||
#include <Tracy.hpp> // NOLINT
|
||||
|
||||
namespace data {
|
||||
/// Thread safe queue with unique keys updating priority and value
|
||||
|
@ -18,12 +19,12 @@ namespace data {
|
|||
|
||||
std::vector<std::pair<K, W>> heap;
|
||||
robin_hood::unordered_map<K, V, hash> map;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
TracyLockableN(std::mutex, mutex, "PriorityQueueMap");
|
||||
std::condition_variable_any cv;
|
||||
|
||||
public:
|
||||
void push(const K& key, const V& val, const W& weight) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
heap.emplace_back(key, weight);
|
||||
std::push_heap(heap.begin(), heap.end(), cmpByWeight);
|
||||
map.insert_or_assign(key, val);
|
||||
|
@ -31,7 +32,7 @@ namespace data {
|
|||
}
|
||||
|
||||
bool pop(std::pair<K, V>& out) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
if (heap.empty())
|
||||
return false;
|
||||
|
||||
|
@ -49,12 +50,12 @@ namespace data {
|
|||
}
|
||||
|
||||
bool empty() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
return heap.empty();
|
||||
}
|
||||
|
||||
size_t size() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
return map.size();
|
||||
}
|
||||
|
||||
|
@ -66,7 +67,7 @@ namespace data {
|
|||
}
|
||||
|
||||
void wait() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
if (heap.empty())
|
||||
cv.wait(lock);
|
||||
}
|
||||
|
@ -83,12 +84,12 @@ namespace data {
|
|||
|
||||
std::vector<std::pair<K, W>> heap;
|
||||
robin_hood::unordered_flat_set<K> set;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
TracyLockableN(std::mutex, mutex, "PriorityQueue");
|
||||
std::condition_variable_any cv;
|
||||
|
||||
public:
|
||||
void push(const K& key, const W& weight) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
heap.emplace_back(key, weight);
|
||||
std::push_heap(heap.begin(), heap.end(), cmpByWeight);
|
||||
set.insert(key);
|
||||
|
@ -96,7 +97,7 @@ namespace data {
|
|||
}
|
||||
|
||||
bool pop(K& out) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
if (heap.empty())
|
||||
return false;
|
||||
|
||||
|
@ -114,12 +115,12 @@ namespace data {
|
|||
}
|
||||
|
||||
bool empty() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
return heap.empty();
|
||||
}
|
||||
|
||||
size_t size() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
return set.size();
|
||||
}
|
||||
|
||||
|
@ -131,7 +132,7 @@ namespace data {
|
|||
}
|
||||
|
||||
void wait() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
if (heap.empty())
|
||||
cv.wait(lock);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <Tracy.hpp> // NOLINT
|
||||
|
||||
namespace data {
|
||||
/// Thread safe queue
|
||||
|
@ -10,25 +11,25 @@ namespace data {
|
|||
class safe_queue {
|
||||
private:
|
||||
std::queue<T> queue;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
TracyLockableN(std::mutex, mutex, "Queue");
|
||||
std::condition_variable_any cv;
|
||||
|
||||
public:
|
||||
void push(const T& in) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
queue.push(in);
|
||||
cv.notify_one();
|
||||
}
|
||||
|
||||
template <typename... _Args>
|
||||
void emplace(_Args &&... __args) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
queue.emplace(std::forward<_Args>(__args)...);
|
||||
cv.notify_one();
|
||||
}
|
||||
|
||||
bool pop(T& out) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
if (queue.empty())
|
||||
return false;
|
||||
|
||||
|
@ -38,12 +39,12 @@ namespace data {
|
|||
}
|
||||
|
||||
bool empty() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
return queue.empty();
|
||||
}
|
||||
|
||||
size_t size() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
return queue.size();
|
||||
}
|
||||
|
||||
|
@ -55,7 +56,7 @@ namespace data {
|
|||
}
|
||||
|
||||
void wait() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
if(queue.empty())
|
||||
cv.wait(lock);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace data {
|
|||
|
||||
public:
|
||||
bool push(const T& in) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
if(set.insert(in).second) {
|
||||
queue.push(in);
|
||||
cv.notify_one();
|
||||
|
@ -27,7 +27,7 @@ namespace data {
|
|||
}
|
||||
|
||||
bool pop(T& out) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
if (queue.empty())
|
||||
return false;
|
||||
|
||||
|
@ -38,7 +38,7 @@ namespace data {
|
|||
}
|
||||
|
||||
bool take(T& out) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
if (queue.empty())
|
||||
return false;
|
||||
|
||||
|
@ -48,17 +48,17 @@ namespace data {
|
|||
}
|
||||
|
||||
void release(const T& taken) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
set.erase(taken);
|
||||
}
|
||||
|
||||
bool empty() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
return queue.empty();
|
||||
}
|
||||
|
||||
size_t size() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
return set.size();
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ namespace data {
|
|||
}
|
||||
|
||||
void wait() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<LockableBase(std::mutex)> lock(mutex);
|
||||
if(queue.empty())
|
||||
cv.wait(lock);
|
||||
}
|
||||
|
|
77
src/main.cpp
77
src/main.cpp
|
@ -20,7 +20,22 @@
|
|||
#include "state.h"
|
||||
#include "data/math.hpp"
|
||||
|
||||
#include <Remotery.h> // NOLINT
|
||||
#include <Tracy.hpp>
|
||||
#include <TracyOpenGL.hpp>
|
||||
|
||||
#if TRACY_MEMORY
|
||||
void *operator new(std::size_t count)
|
||||
{
|
||||
auto ptr = malloc(count);
|
||||
TracyAlloc(ptr, count);
|
||||
return ptr;
|
||||
}
|
||||
void operator delete(void *ptr) noexcept
|
||||
{
|
||||
TracyFree(ptr);
|
||||
free(ptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Entry point
|
||||
int main(int /*unused*/, char */*unused*/[]){
|
||||
|
@ -30,7 +45,7 @@ int main(int /*unused*/, char */*unused*/[]){
|
|||
reports reports;
|
||||
|
||||
GLFWwindow *window = createWindow(options.samples);
|
||||
if(window == NULL)
|
||||
if(window == nullptr)
|
||||
return 1;
|
||||
|
||||
glClearColor(options.renderer.clear_color.x, options.renderer.clear_color.y, options.renderer.clear_color.z, options.renderer.clear_color.w);
|
||||
|
@ -39,13 +54,13 @@ int main(int /*unused*/, char */*unused*/[]){
|
|||
InputMap inputs(window);
|
||||
Camera camera(window, inputs, options.camera);
|
||||
|
||||
Renderer *renderer = new Renderer(options.renderer);
|
||||
auto *renderer = new Renderer(options.renderer);
|
||||
renderer->LightInvDir = glm::vec3(-0.5f, 2, -2);
|
||||
UI::setup(window);
|
||||
|
||||
GLuint aimTexture = pass::Program::loadTexture("ui/Aim", false);
|
||||
|
||||
pass::ColorProgram *lookProgram = new pass::ColorProgram();
|
||||
auto *lookProgram = new pass::ColorProgram();
|
||||
buffer::Colored lookBuffer(GL_LINES, 24, {
|
||||
glm::vec3(0, 0, 0), glm::vec3(0, 0, 1),
|
||||
glm::vec3(0, 0, 1), glm::vec3(0, 1, 1),
|
||||
|
@ -74,12 +89,10 @@ int main(int /*unused*/, char */*unused*/[]){
|
|||
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
|
||||
});
|
||||
|
||||
#if RMT_ENABLED
|
||||
Remotery *rmt;
|
||||
rmt_CreateGlobalInstance(&rmt);
|
||||
rmt_BindOpenGL();
|
||||
#if TRACY_ENABLE
|
||||
LOG("Profiling !");
|
||||
#endif
|
||||
TracyGpuContext;
|
||||
|
||||
world::Universe world = world::Universe(options.world);
|
||||
world.setContouring(contouring::load(options.contouring_idx, options.contouring_data));
|
||||
|
@ -88,10 +101,10 @@ int main(int /*unused*/, char */*unused*/[]){
|
|||
do {
|
||||
const double startTime = glfwGetTime();
|
||||
{ // Update
|
||||
rmt_ScopedCPUSample(Update, 0);
|
||||
ZoneScopedN("Update");
|
||||
static double lastTime = glfwGetTime();
|
||||
const double partTime = glfwGetTime();
|
||||
const float deltaTime = float(partTime - lastTime);
|
||||
const float deltaTime = partTime - lastTime;
|
||||
inputs.toggle(state.capture_mouse, Input::Mouse);
|
||||
inputs.toggle(options.show_debug_menu, Input::Debug);
|
||||
|
||||
|
@ -111,14 +124,13 @@ int main(int /*unused*/, char */*unused*/[]){
|
|||
world.addEntity(entity_id(0), {state.position * options.voxel_density, glm::vec3(10, 0, 0)});
|
||||
}
|
||||
}
|
||||
world.update((state.position * options.voxel_density).as_voxel(), deltaTime, reports.world);
|
||||
world.update((state.position * options.voxel_density).as_voxel(), deltaTime);
|
||||
inputs.saveKeys();
|
||||
reports.main.update.push((glfwGetTime() - partTime) * 1000);
|
||||
lastTime = partTime;
|
||||
}
|
||||
|
||||
{
|
||||
rmt_ScopedCPUSample(UI, 0);
|
||||
ZoneScopedN("UI");
|
||||
const auto actions = UI::draw(options, state, reports, aimTexture);
|
||||
if (actions && UI::Actions::FPS) {
|
||||
glfwSwapInterval(static_cast<int>(options.target_fps < MIN_FPS));
|
||||
|
@ -130,7 +142,7 @@ int main(int /*unused*/, char */*unused*/[]){
|
|||
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
|
||||
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
|
||||
} else {
|
||||
glfwSetWindowMonitor(window, NULL, 0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT, GLFW_DONT_CARE);
|
||||
glfwSetWindowMonitor(window, nullptr, 0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT, GLFW_DONT_CARE);
|
||||
}
|
||||
}
|
||||
if(actions && UI::Actions::ClearColor) {
|
||||
|
@ -157,13 +169,13 @@ int main(int /*unused*/, char */*unused*/[]){
|
|||
renderer->SkyEnable = options.renderer.skybox;
|
||||
}
|
||||
{ // Rendering
|
||||
rmt_ScopedCPUSample(Render, 0);
|
||||
const double partTime = glfwGetTime();
|
||||
ZoneScopedNS("Render", 5);
|
||||
TracyGpuZone("Render");
|
||||
auto pass = renderer->getPass();
|
||||
pass.start();
|
||||
|
||||
reports.main.models_count = 0;
|
||||
reports.main.tris_count = 0;
|
||||
reports.models_count = 0;
|
||||
reports.tris_count = 0;
|
||||
std::optional<geometry::Frustum> frustum;
|
||||
if(options.culling) {
|
||||
frustum = {camera.getFrustum()};
|
||||
|
@ -172,10 +184,9 @@ int main(int /*unused*/, char */*unused*/[]){
|
|||
{ // Chunks
|
||||
std::vector<std::pair<glm::mat4, buffer::Abstract *const>> models;
|
||||
world.getContouring()->getModels(models, frustum, offset, options.voxel_density);
|
||||
rmt_ScopedOpenGLSample(Render);
|
||||
for (auto [model, buffer] : models) {
|
||||
reports.main.models_count++;
|
||||
reports.main.tris_count += buffer->draw(pass.setup(model));
|
||||
reports.models_count++;
|
||||
reports.tris_count += buffer->draw(pass.setup(model));
|
||||
}
|
||||
}
|
||||
{ // Entities
|
||||
|
@ -185,8 +196,8 @@ int main(int /*unused*/, char */*unused*/[]){
|
|||
lookProgram->start(renderer);
|
||||
for (auto [mats, buffer]: models) {
|
||||
for(auto model: mats) {
|
||||
reports.main.models_count++;
|
||||
reports.main.tris_count += lookBuffer.draw(lookProgram->setup(renderer, model));
|
||||
reports.models_count++;
|
||||
reports.tris_count += lookBuffer.draw(lookProgram->setup(renderer, model));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -194,44 +205,38 @@ int main(int /*unused*/, char */*unused*/[]){
|
|||
lookProgram->useIt();
|
||||
lookProgram->start(renderer);
|
||||
const auto model = glm::scale(glm::translate(glm::scale(glm::mat4(1), 1.f / glm::vec3(options.voxel_density)), glm::vec3(state.look_at.value().pos.second + state.look_at.value().offset - offset * glm::llvec3(options.voxel_density)) - glm::vec3(.5 + options.tool.radius)), glm::vec3(1 + options.tool.radius * 2));
|
||||
reports.main.models_count++;
|
||||
reports.main.tris_count += lookBuffer.draw(lookProgram->setup(renderer, model));
|
||||
reports.models_count++;
|
||||
reports.tris_count += lookBuffer.draw(lookProgram->setup(renderer, model));
|
||||
}
|
||||
renderer->postProcess();
|
||||
|
||||
UI::render();
|
||||
reports.main.render.push((glfwGetTime() - partTime) * 1000);
|
||||
}
|
||||
|
||||
{ // Swap buffers
|
||||
rmt_ScopedCPUSample(Swap, 0);
|
||||
rmt_ScopedOpenGLSample(Swap);
|
||||
const double partTime = glfwGetTime();
|
||||
ZoneScopedN("Swap");
|
||||
TracyGpuZone("Swap");
|
||||
glfwSwapBuffers(window);
|
||||
glfwPollEvents();
|
||||
reports.main.swap.push((glfwGetTime() - partTime) * 1000);
|
||||
TracyGpuCollect;
|
||||
FrameMark;
|
||||
}
|
||||
|
||||
{ // Wait target fps
|
||||
const double partTime = glfwGetTime();
|
||||
if(options.target_fps >= MIN_FPS && options.target_fps <= MAX_FPS) {
|
||||
while (glfwGetTime() < startTime + 1.0 / options.target_fps) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||
}
|
||||
}
|
||||
reports.main.wait.push((glfwGetTime() - partTime) * 1000);
|
||||
}
|
||||
reports.main.fps.push(1.0 / (glfwGetTime() - startTime));
|
||||
} // Check if the ESC key was pressed or the window was closed
|
||||
while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS &&
|
||||
glfwWindowShouldClose(window) == 0);
|
||||
|
||||
UI::unload();
|
||||
delete lookProgram;
|
||||
delete renderer;
|
||||
|
||||
rmt_UnbindOpenGL();
|
||||
rmt_DestroyGlobalInstance(rmt);
|
||||
|
||||
// Close OpenGL window and terminate GLFW
|
||||
glfwTerminate();
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ void Renderer::unloadTextures() {
|
|||
}
|
||||
void Renderer::loadTextures(const std::string& texturePath, float mipMapLOD) {
|
||||
std::vector<std::string> terrainTextures;
|
||||
for(const auto texture: world::materials::textures) {
|
||||
for(const auto& texture: world::materials::textures) {
|
||||
terrainTextures.emplace_back(texturePath + "/terrain/" + texture);
|
||||
}
|
||||
TextureAtlas = pass::Program::loadTextureArray(terrainTextures, "", mipMapLOD);
|
||||
|
|
|
@ -14,7 +14,7 @@ public:
|
|||
/// Main pass
|
||||
pass::MainProgram::options main;
|
||||
/// Display skybox
|
||||
bool skybox = false;
|
||||
bool skybox = true;
|
||||
/// Display only wires
|
||||
bool wireframe = false;
|
||||
/// Texture pack name
|
||||
|
|
|
@ -57,12 +57,7 @@ UI::Actions UI::draw(options &options, state &state, const reports &reports, GLu
|
|||
|
||||
if (options.show_debug_render) {
|
||||
ImGui::Begin("Debug: Render", &options.show_debug_render, ImGuiWindowFlags_AlwaysAutoResize);
|
||||
ImGui::PlotHistogram("FPS", reports.main.fps.buffer.get(), reports.main.fps.size, 0, std::to_string(reports.main.fps.current()).c_str(), 0, options.target_fps >= MIN_FPS && options.target_fps <= MAX_FPS ? options.target_fps * 1.2 : FLT_MAX, ImVec2(0, 30));
|
||||
ImGui::PlotHistogram("Update", reports.main.update.buffer.get(), reports.main.update.size, 0, std::to_string(reports.main.update.current()).c_str(), 0);
|
||||
ImGui::PlotHistogram("Render", reports.main.render.buffer.get(), reports.main.render.size, 0, std::to_string(reports.main.render.current()).c_str(), 0);
|
||||
ImGui::PlotHistogram("Swap", reports.main.swap.buffer.get(), reports.main.swap.size, 0, std::to_string(reports.main.swap.current()).c_str(), 0);
|
||||
ImGui::PlotHistogram("Wait", reports.main.wait.buffer.get(), reports.main.wait.size, 0, std::to_string(reports.main.wait.current()).c_str(), 0);
|
||||
ImGui::Text("Tris: %ld (%ld models)", reports.main.tris_count, reports.main.models_count);
|
||||
ImGui::Text("Tris: %ld (%ld models)", reports.tris_count, reports.models_count);
|
||||
ImGui::Separator();
|
||||
ImGui::Checkbox("Overlay", &options.overlay_show);
|
||||
if (ImGui::SliderInt("FPS", &options.target_fps, MIN_FPS-1, MAX_FPS+1, options.target_fps > MIN_FPS ? (options.target_fps < MAX_FPS ? "%d" : "UNLIMITED") : "VSYNC")){
|
||||
|
@ -109,12 +104,6 @@ UI::Actions UI::draw(options &options, state &state, const reports &reports, GLu
|
|||
|
||||
if (options.show_debug_world) {
|
||||
ImGui::Begin("Debug: World", &options.show_debug_world, ImGuiWindowFlags_AlwaysAutoResize);
|
||||
ImGui::PlotHistogram("Count", reports.world.chunk_count.buffer.get(), reports.world.chunk_count.size, 0, std::to_string(reports.world.chunk_count.current()).c_str(), 0);
|
||||
ImGui::PlotHistogram("Loading", reports.world.chunk_load.buffer.get(), reports.world.chunk_load.size, 0, std::to_string(reports.world.chunk_load.current()).c_str(), 0);
|
||||
ImGui::PlotHistogram("Saving", reports.world.chunk_unload.buffer.get(), reports.world.chunk_unload.size, 0, std::to_string(reports.world.chunk_unload.current()).c_str(), 0);
|
||||
ImGui::PlotHistogram("Regions", reports.world.region_count.buffer.get(), reports.world.region_count.size, 0, std::to_string(reports.world.region_count.current()).c_str(), 0);
|
||||
ImGui::PlotHistogram("Entities", reports.world.entity_count.buffer.get(), reports.world.entity_count.size, 0, std::to_string(reports.world.entity_count.current()).c_str(), 0);
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Path: %s", options.world.folderPath.c_str());
|
||||
if (ImGui::SliderInt("Load distance", &options.world.loadDistance, 1, options.world.keepDistance) |
|
||||
ImGui::SliderInt("Keep distance", &options.world.keepDistance, options.world.loadDistance + 1, 21)) {
|
||||
|
@ -239,7 +228,7 @@ UI::Actions UI::draw(options &options, state &state, const reports &reports, GLu
|
|||
ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background
|
||||
ImGui::Begin("Overlay", &options.overlay_show, (options.overlay_corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav);
|
||||
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
|
||||
ImGui::Text("%ld tris(%ld models)", reports.main.tris_count, reports.main.models_count);
|
||||
ImGui::Text("%ld tris(%ld models)", reports.tris_count, reports.models_count);
|
||||
if (ImGui::BeginPopupContextWindow()) {
|
||||
if (ImGui::MenuItem("Custom", NULL, options.overlay_corner == -1))
|
||||
options.overlay_corner = -1;
|
||||
|
|
|
@ -13,9 +13,9 @@ namespace pass {
|
|||
/// Triplanar texture mapping
|
||||
bool triplanar = false;
|
||||
/// Active geometry pass
|
||||
bool geometry = true;
|
||||
bool geometry = false;
|
||||
/// Blend voxel with mixed materials (requires geometry)
|
||||
bool blend = false;
|
||||
bool blend = true;
|
||||
/// Depth fog
|
||||
bool fog = true;
|
||||
};
|
||||
|
|
12
src/state.h
12
src/state.h
|
@ -192,14 +192,6 @@ struct state {
|
|||
|
||||
/// Readonly metrics
|
||||
struct reports {
|
||||
struct main {
|
||||
size_t tris_count = 0;
|
||||
size_t models_count = 0;
|
||||
report_buffer fps;
|
||||
report_buffer update;
|
||||
report_buffer render;
|
||||
report_buffer swap;
|
||||
report_buffer wait;
|
||||
} main;
|
||||
world::Universe::report world;
|
||||
size_t tris_count = 0;
|
||||
size_t models_count = 0;
|
||||
};
|
|
@ -8,12 +8,14 @@ using namespace world;
|
|||
|
||||
constexpr auto DENSITY = 0.f;
|
||||
constexpr auto GRANULARITY = 30.f;
|
||||
constexpr auto HEIGHT = 2;
|
||||
|
||||
Chunk::Chunk(const chunk_pos& pos, Generator& rnd) {
|
||||
const auto [densitySet, materialSet] = rnd.getChunk(pos, CHUNK_LENGTH);
|
||||
|
||||
for (size_t i = 0; i < CHUNK_SIZE; i++) {
|
||||
const auto density = std::clamp((densitySet[i] + DENSITY) * GRANULARITY, 0.f, 1.f) * Voxel::DENSITY_MAX;
|
||||
const auto height = std::max(0l, pos.y * CHUNK_LENGTH + glm::fromIdx(i).y);
|
||||
const auto density = std::clamp((densitySet[i] + DENSITY) * GRANULARITY - height / HEIGHT, 0.f, 1.f) * Voxel::DENSITY_MAX;
|
||||
const auto material = density > 0 ? 1 + std::clamp(static_cast<int>(std::lrint((materialSet[i] + 1) / 2 * (materials::count - 2))),
|
||||
0, materials::count - 2) : 0; //NOTE: map (approx -1, 1) to (1, mat_max)
|
||||
voxels[i] = Voxel(material, density);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "Universe.hpp"
|
||||
|
||||
#include <Remotery.h> // NOLINT
|
||||
#include <Tracy.hpp> //NOLINT
|
||||
#include <common/TracySystem.hpp> // NOLINT
|
||||
#include <filesystem>
|
||||
|
||||
#include "../contouring/Dummy.hpp"
|
||||
|
@ -26,7 +27,7 @@ Universe::Universe(const Universe::options &options): dicts("content/zstd.dict"
|
|||
if(index.good()) {
|
||||
size_t size = 0;
|
||||
index.read(reinterpret_cast<char *>(&size), sizeof(size));
|
||||
std::map<size_t, Area::params> tmp;
|
||||
robin_hood::unordered_map<size_t, Area::params> tmp;
|
||||
while(!index.eof()) {
|
||||
size_t id = UINT32_MAX;
|
||||
index.read(reinterpret_cast<char *>(&id), sizeof(size_t));
|
||||
|
@ -48,7 +49,7 @@ Universe::Universe(const Universe::options &options): dicts("content/zstd.dict"
|
|||
LOG_E("No index file!!! Probably a new world...");
|
||||
//TODO: generate universe
|
||||
far_areas.emplace(Area::params{voxel_pos(0), 1 << 20});
|
||||
far_areas.emplace(Area::params{voxel_pos(0), 1, 43});
|
||||
//far_areas.emplace(Area::params{voxel_pos(0), 1, 43});
|
||||
}
|
||||
index.close();
|
||||
}
|
||||
|
@ -58,28 +59,31 @@ Universe::Universe(const Universe::options &options): dicts("content/zstd.dict"
|
|||
// Workers
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
workers.emplace_back([&] {
|
||||
#if TRACY_ENABLE
|
||||
tracy::SetThreadName("Chunks");
|
||||
#endif
|
||||
const auto read_ctx = dicts.make_reader();
|
||||
const auto write_ctx = dicts.make_writer();
|
||||
while (running) {
|
||||
if (std::pair<area_<chunk_pos>, std::shared_ptr<Area>> task; loadQueue.pop(task)) {
|
||||
//MAYBE: loadQueue.take to avoid duplicated work on fast move
|
||||
rmt_ScopedCPUSample(ProcessLoad, 0);
|
||||
ZoneScopedN("ProcessLoad");
|
||||
const auto &pos = task.first;
|
||||
const auto rcPos = glm::split(pos.second);
|
||||
const auto reg = task.second->getRegion(folderPath, std::make_pair(pos.first, rcPos.first));
|
||||
Region::data data;
|
||||
if(reg->read(rcPos.second, read_ctx, data)) {
|
||||
rmt_ScopedCPUSample(ProcessRead, 0);
|
||||
ZoneScopedN("ProcessRead");
|
||||
vec_istream idata(data);
|
||||
std::istream iss(&idata);
|
||||
loadedQueue.push({pos, std::make_shared<Chunk>(iss)});
|
||||
} else {
|
||||
rmt_ScopedCPUSample(ProcessGenerate, 0);
|
||||
ZoneScopedN("ProcessGenerate");
|
||||
loadedQueue.push({pos, std::make_shared<Chunk>(pos.second, task.second->getGenerator())});
|
||||
}
|
||||
} else if(save_task_t task; saveQueue.pop(task)) {
|
||||
//MAYBE: queue.take to avoid concurent write or duplicated work on fast move
|
||||
rmt_ScopedCPUSample(ProcessSave, 0);
|
||||
ZoneScopedN("ProcessSave");
|
||||
if(task.second.second->isModified()) {
|
||||
std::ostringstream out;
|
||||
task.second.second->write(out);
|
||||
|
@ -157,14 +161,14 @@ void Universe::saveAreas() const {
|
|||
index.close();
|
||||
}
|
||||
|
||||
void Universe::update(const voxel_pos& pos, float deltaTime, Universe::report& rep) {
|
||||
rmt_ScopedCPUSample(Universe, 0);
|
||||
void Universe::update(const voxel_pos& pos, float deltaTime) {
|
||||
ZoneScopedN("Universe");
|
||||
const chunk_pos newPos = glm::divide(pos);
|
||||
const auto chunkChange = last_pos != newPos;
|
||||
last_pos = newPos;
|
||||
|
||||
if(chunkChange) {
|
||||
rmt_ScopedCPUSample(Far, 0);
|
||||
ZoneScopedN("Far");
|
||||
far_areas.extract([&](area_id id, Area::params params){
|
||||
if (const chunk_pos diff = glm::divide(pos - params.center);
|
||||
glm::length2(diff) > glm::pow2(loadDistance + params.radius))
|
||||
|
@ -176,14 +180,16 @@ void Universe::update(const voxel_pos& pos, float deltaTime, Universe::report& r
|
|||
});
|
||||
}
|
||||
{ // Update alive areas
|
||||
rmt_ScopedCPUSample(World, 0);
|
||||
ZoneScopedN("World");
|
||||
#if TRACY_ENABLE
|
||||
size_t chunk_count = 0;
|
||||
size_t region_count = 0;
|
||||
#endif
|
||||
const bool queuesEmpty = loadQueue.empty() && saveQueue.empty();
|
||||
bool allLazy = true;
|
||||
auto it = areas.begin();
|
||||
while (it != areas.end()) {
|
||||
rmt_ScopedCPUSample(Area, 0);
|
||||
ZoneScopedN("Area");
|
||||
const bool chunkChangeArea = (false && it->first == 1 && it->second->move(glm::vec3(deltaTime))) || chunkChange; // TODO: area.velocity
|
||||
const chunk_pos diff = glm::divide(pos - it->second->getOffset().as_voxel());
|
||||
auto &chunks = it->second->setChunks();
|
||||
|
@ -202,7 +208,7 @@ void Universe::update(const voxel_pos& pos, float deltaTime, Universe::report& r
|
|||
} else {
|
||||
bool lazyArea = queuesEmpty;
|
||||
{ // Update alive chunks
|
||||
rmt_ScopedCPUSample(Alive, 0);
|
||||
ZoneScopedN("Alive");
|
||||
auto it_c = chunks.begin();
|
||||
while(it_c != chunks.end()) {
|
||||
if (glm::length2(diff - it_c->first) > glm::pow2(keepDistance)) {
|
||||
|
@ -217,12 +223,14 @@ void Universe::update(const voxel_pos& pos, float deltaTime, Universe::report& r
|
|||
contouring->onNotify(acPos, diff, chunks);
|
||||
}
|
||||
++it_c;
|
||||
#if TRACY_ENABLE
|
||||
chunk_count++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunkChangeArea) { // Enqueue missing chunks
|
||||
rmt_ScopedCPUSample(Missing, 0);
|
||||
ZoneScopedN("Missing");
|
||||
//TODO: need dist so no easy sphere fill
|
||||
for (int x = -loadDistance; x <= loadDistance; x++) {
|
||||
for (int y = -loadDistance; y <= loadDistance; y++) {
|
||||
|
@ -239,9 +247,11 @@ void Universe::update(const voxel_pos& pos, float deltaTime, Universe::report& r
|
|||
}
|
||||
allLazy &= lazyArea;
|
||||
if (lazyArea) { // Clear un-used regions
|
||||
rmt_ScopedCPUSample(Region, 0);
|
||||
ZoneScopedN("Region");
|
||||
const auto unique = it->second->getRegions(); // MAYBE: shared then unique
|
||||
#if TRACY_ENABLE
|
||||
region_count += unique->size();
|
||||
#endif
|
||||
for (auto it_r = unique->begin(); it_r != unique->end(); ++it_r) {
|
||||
if (glm::length2(diff - glm::lvec3(it_r->first) * glm::lvec3(REGION_LENGTH)) > glm::pow2(keepDistance + REGION_LENGTH * 2)) {
|
||||
unique->erase(it_r); //FIXME: may wait for os file access (long)
|
||||
|
@ -252,31 +262,41 @@ void Universe::update(const voxel_pos& pos, float deltaTime, Universe::report& r
|
|||
++it;
|
||||
}
|
||||
}
|
||||
rep.chunk_count.push(chunk_count);
|
||||
rep.region_count.push(allLazy ? region_count : rep.region_count.current());
|
||||
#if TRACY_ENABLE
|
||||
TracyPlot("ChunkCount", static_cast<int64_t>(chunk_count));
|
||||
if(allLazy) {
|
||||
TracyPlot("Region", static_cast<int64_t>(region_count));
|
||||
}
|
||||
TracyPlot("ChunkLoad", static_cast<int64_t>(loadQueue.size()));
|
||||
TracyPlot("ChunkUnload", static_cast<int64_t>(saveQueue.size()));
|
||||
#endif
|
||||
}
|
||||
rep.chunk_load.push(loadQueue.size());
|
||||
rep.chunk_unload.push(saveQueue.size());
|
||||
{
|
||||
rmt_ScopedCPUSample(Contouring, 0);
|
||||
ZoneScopedN("Contouring");
|
||||
contouring->update(pos, areas);
|
||||
//MAYBE: if(chunkChange) contouring->notify(chunks);
|
||||
}
|
||||
{ // Update entities
|
||||
rmt_ScopedCPUSample(Entities, 0);
|
||||
ZoneScopedN("Entities");
|
||||
#if TRACY_ENABLE
|
||||
size_t entity_count = 0;
|
||||
#endif
|
||||
entities.for_each([&](entity_id, Entity &val) {
|
||||
val.instances.remove([&](entity_id, Entity::Instance &inst) {
|
||||
#if TRACY_ENABLE
|
||||
entity_count++;
|
||||
#endif
|
||||
inst.pos += inst.velocity * deltaTime;
|
||||
return glm::length2(glm::divide(pos - inst.pos.as_voxel())) > glm::pow2(keepDistance);
|
||||
});
|
||||
});
|
||||
rep.entity_count.push(entity_count);
|
||||
#if TRACY_ENABLE
|
||||
TracyPlot("EntityCount", static_cast<int64_t>(entity_count));
|
||||
#endif
|
||||
}
|
||||
|
||||
{ // Store loaded chunks
|
||||
rmt_ScopedCPUSample(Load, 0);
|
||||
ZoneScopedN("Load");
|
||||
robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> loaded;
|
||||
while (loadedQueue.pop(loaded)) {
|
||||
if (const auto it = areas.find(loaded.first.first); it != areas.end()) {
|
||||
|
|
|
@ -34,25 +34,12 @@ namespace world {
|
|||
/// Storage path
|
||||
std::string folderPath = "world";
|
||||
};
|
||||
/// Reports to UI
|
||||
struct report {
|
||||
/// Chunks in memory
|
||||
report_buffer chunk_count;
|
||||
/// Loaded chunks
|
||||
report_buffer chunk_load;
|
||||
/// Saved chunks
|
||||
report_buffer chunk_unload;
|
||||
/// Regions in memory
|
||||
report_buffer region_count;
|
||||
/// Entity instances
|
||||
report_buffer entity_count;
|
||||
};
|
||||
|
||||
Universe(const options &);
|
||||
~Universe();
|
||||
|
||||
/// Update physics and contouring
|
||||
void update(const voxel_pos &pos, float deltaTime, report &rep);
|
||||
void update(const voxel_pos &pos, float deltaTime);
|
||||
/// Apply new options
|
||||
void setOptions(const options &);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <robin_hood.h>
|
||||
|
||||
namespace world {
|
||||
/// Universe unit
|
||||
|
@ -44,7 +44,7 @@ namespace world {
|
|||
Voxel::material_t Material;
|
||||
};
|
||||
/// List of materials
|
||||
struct ItemList: std::map<unsigned short, unsigned long long> {
|
||||
struct ItemList: robin_hood::unordered_map<Voxel::material_t, unsigned long long> {
|
||||
void add(const std::optional<Item>& item) {
|
||||
if(item) {
|
||||
(*this)[item.value().Material] += item.value().Count;
|
||||
|
|
Loading…
Reference in New Issue