Compare commits
28 commits
6b255ceb59
...
a75119a8c2
Author | SHA1 | Date | |
---|---|---|---|
a75119a8c2 | |||
acc1f93f27 | |||
bb2b0a2762 | |||
86b750b1a3 | |||
41c5b74527 | |||
fcf4343509 | |||
783fd17563 | |||
e40b594ae5 | |||
133ca0bb5e | |||
e28436b76c | |||
c628fa2b83 | |||
c63e91285b | |||
5c8c39cc33 | |||
75f0892748 | |||
a5bbfe53f2 | |||
2047ed6d65 | |||
74b8bca10a | |||
9f97a90c12 | |||
f305595b8f | |||
c54580bdeb | |||
1f15662c2d | |||
a10b34dc94 | |||
f27e778f1c | |||
284a176158 | |||
d558f166f9 | |||
527f159ff3 | |||
1e5e60bbcf | |||
19f048b52a |
16
assets/font/AUTHORS
Normal file
|
@ -0,0 +1,16 @@
|
|||
AUTHORS
|
||||
|
||||
Current Contributors (sorted alphabetically):
|
||||
|
||||
- Vishal Vijayraghavan <vishalvvr at fedoraproject dot org>
|
||||
Project Owner/ Maintainer (Current)
|
||||
Red Hat, Inc.
|
||||
|
||||
Previous Contributors
|
||||
- Pravin Satpute <psatpute at redhat dot com>
|
||||
Project Owner/ Maintainer
|
||||
Red Hat, Inc.
|
||||
|
||||
- Steve Matteson
|
||||
Original Designer
|
||||
Ascender, Inc.
|
102
assets/font/LICENSE
Normal file
|
@ -0,0 +1,102 @@
|
|||
Digitized data copyright (c) 2010 Google Corporation
|
||||
with Reserved Font Arimo, Tinos and Cousine.
|
||||
Copyright (c) 2012 Red Hat, Inc.
|
||||
with Reserved Font Name Liberation.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
|
||||
PREAMBLE The goals of the Open Font License (OFL) are to stimulate
|
||||
worldwide development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to provide
|
||||
a free and open framework in which fonts may be shared and improved in
|
||||
partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves.
|
||||
The fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such.
|
||||
This may include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components
|
||||
as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting ? in part or in whole ?
|
||||
any of the components of the Original Version, by changing formats or
|
||||
by porting the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical writer
|
||||
or other person who contributed to the Font Software.
|
||||
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole, must
|
||||
be distributed entirely under this license, and must not be distributed
|
||||
under any other license. The requirement for fonts to remain under
|
||||
this license does not apply to any document created using the Font
|
||||
Software.
|
||||
|
||||
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are not met.
|
||||
|
||||
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
||||
DEALINGS IN THE FONT SOFTWARE.
|
||||
|
BIN
assets/font/LiberationMono-Bold.ttf
Normal file
BIN
assets/font/LiberationMono-BoldItalic.ttf
Normal file
BIN
assets/font/LiberationMono-Italic.ttf
Normal file
BIN
assets/font/LiberationMono-Regular.ttf
Normal file
BIN
assets/font/LiberationSans-Bold.ttf
Normal file
BIN
assets/font/LiberationSans-BoldItalic.ttf
Normal file
BIN
assets/font/LiberationSans-Italic.ttf
Normal file
BIN
assets/font/LiberationSans-Regular.ttf
Normal file
BIN
assets/font/LiberationSerif-Bold.ttf
Normal file
BIN
assets/font/LiberationSerif-BoldItalic.ttf
Normal file
BIN
assets/font/LiberationSerif-Italic.ttf
Normal file
BIN
assets/font/LiberationSerif-Regular.ttf
Normal file
1
assets/icons/editor/actions/48/NOTE.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Icons transform-move, transform-scale, and transform-rotate were modified from silk icons by maelstrom
|
BIN
assets/icons/editor/actions/48/edit-redo.png
Normal file
After Width: | Height: | Size: 625 B |
BIN
assets/icons/editor/actions/48/edit-undo.png
Normal file
After Width: | Height: | Size: 631 B |
BIN
assets/icons/editor/actions/48/object-group.png
Normal file
After Width: | Height: | Size: 553 B |
BIN
assets/icons/editor/actions/48/object-ungroup.png
Normal file
After Width: | Height: | Size: 666 B |
BIN
assets/icons/editor/actions/48/transform-move.png
Normal file
After Width: | Height: | Size: 355 B |
BIN
assets/icons/editor/actions/48/transform-rotate.png
Normal file
After Width: | Height: | Size: 443 B |
BIN
assets/icons/editor/actions/48/transform-scale.png
Normal file
After Width: | Height: | Size: 512 B |
BIN
assets/icons/message.png
Normal file
After Width: | Height: | Size: 413 B |
22
assets/shaders/debug/debugfont.fs
Normal file
|
@ -0,0 +1,22 @@
|
|||
#version 330 core
|
||||
|
||||
out vec4 FragColor;
|
||||
in vec3 vPos;
|
||||
in vec2 vTexCoord;
|
||||
|
||||
uniform sampler2D fontTex;
|
||||
uniform int charIndex;
|
||||
|
||||
// Main
|
||||
|
||||
void main() {
|
||||
int x = (charIndex-32) % 16;
|
||||
int y = (charIndex-32) / 16;
|
||||
|
||||
float fx = float(x) / 16;
|
||||
float fy = float(y) / 8;
|
||||
|
||||
vec4 color = texture(fontTex, vec2(fx, fy) + vTexCoord * vec2(1.f/32, 1.f/16));
|
||||
FragColor = vec3(color) == vec3(0, 0, 0) ? vec4(0, 0, 0, 0) : color;
|
||||
// FragColor = color;
|
||||
}
|
12
assets/shaders/debug/debugfont.vs
Normal file
|
@ -0,0 +1,12 @@
|
|||
#version 330 core
|
||||
in vec3 aPos;
|
||||
in vec2 aTexCoord;
|
||||
|
||||
out vec3 vPos;
|
||||
out vec2 vTexCoord;
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(aPos, 1.0);
|
||||
vPos = aPos;
|
||||
vTexCoord = aTexCoord;
|
||||
}
|
|
@ -1,22 +1,14 @@
|
|||
// https://learnopengl.com/In-Practice/Text-Rendering
|
||||
|
||||
#version 330 core
|
||||
in vec2 TexCoords;
|
||||
out vec4 color;
|
||||
|
||||
out vec4 FragColor;
|
||||
in vec3 vPos;
|
||||
in vec2 vTexCoord;
|
||||
uniform sampler2D text;
|
||||
uniform vec3 textColor;
|
||||
|
||||
uniform sampler2D fontTex;
|
||||
uniform int charIndex;
|
||||
|
||||
// Main
|
||||
|
||||
void main() {
|
||||
int x = (charIndex-32) % 16;
|
||||
int y = (charIndex-32) / 16;
|
||||
|
||||
float fx = float(x) / 16;
|
||||
float fy = float(y) / 8;
|
||||
|
||||
vec4 color = texture(fontTex, vec2(fx, fy) + vTexCoord * vec2(1.f/32, 1.f/16));
|
||||
FragColor = vec3(color) == vec3(0, 0, 0) ? vec4(0, 0, 0, 0) : color;
|
||||
// FragColor = color;
|
||||
}
|
||||
void main()
|
||||
{
|
||||
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
|
||||
color = vec4(textColor, 1.0) * sampled;
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
#version 330 core
|
||||
in vec3 aPos;
|
||||
in vec2 aTexCoord;
|
||||
// https://learnopengl.com/In-Practice/Text-Rendering
|
||||
|
||||
#version 330 core
|
||||
layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
|
||||
out vec2 TexCoords;
|
||||
|
||||
uniform mat4 projection;
|
||||
|
||||
out vec3 vPos;
|
||||
out vec2 vTexCoord;
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(aPos, 1.0);
|
||||
vPos = aPos;
|
||||
vTexCoord = aTexCoord;
|
||||
}
|
||||
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
|
||||
TexCoords = vertex.zw;
|
||||
}
|
|
@ -3,10 +3,10 @@
|
|||
// I/O
|
||||
|
||||
out vec4 fColor;
|
||||
uniform vec3 aColor;
|
||||
uniform vec4 aColor;
|
||||
|
||||
// Main
|
||||
|
||||
void main() {
|
||||
fColor = vec4(aColor, 1.0);
|
||||
fColor = aColor;
|
||||
}
|
|
@ -34,7 +34,7 @@ int main() {
|
|||
glewInit();
|
||||
|
||||
gDataModel->Init();
|
||||
renderInit(window, 1200, 900);
|
||||
renderInit(1200, 900);
|
||||
|
||||
// Baseplate
|
||||
gWorkspace()->AddChild(Part::New({
|
||||
|
@ -66,7 +66,7 @@ int main() {
|
|||
|
||||
processInput(window);
|
||||
gWorkspace()->PhysicsStep(deltaTime);
|
||||
render(window);
|
||||
render();
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
glfwPollEvents();
|
||||
|
@ -77,7 +77,7 @@ int main() {
|
|||
}
|
||||
|
||||
void errorCatcher(int id, const char* str) {
|
||||
Logger::fatalError(std::format("GLFW Error: [{}] {}", id, str));
|
||||
Logger::fatalErrorf("GLFW Error: [{}] {}", id, str);
|
||||
}
|
||||
|
||||
float lastTime;
|
||||
|
|
|
@ -7,6 +7,7 @@ find_package(OpenGL)
|
|||
find_package(glm CONFIG REQUIRED)
|
||||
find_package(ReactPhysics3D REQUIRED)
|
||||
find_package(pugixml 1.15 REQUIRED)
|
||||
find_package(Freetype)
|
||||
|
||||
find_package(Stb REQUIRED)
|
||||
include_directories(${Stb_INCLUDE_DIR})
|
||||
|
@ -44,7 +45,7 @@ list(APPEND SOURCES ${AUTOGEN_OUTS})
|
|||
add_library(openblocks STATIC ${SOURCES})
|
||||
set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
|
||||
target_link_directories(openblocks PUBLIC ${LUAJIT_LIBRARY_DIRS})
|
||||
target_link_libraries(openblocks ${GLEW_LIBRARIES} ${LUAJIT_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
|
||||
target_link_libraries(openblocks ${GLEW_LIBRARIES} ${LUAJIT_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml Freetype::Freetype)
|
||||
target_include_directories(openblocks PUBLIC "src" "../include" ${LUAJIT_INCLUDE_DIRS})
|
||||
add_dependencies(openblocks autogen_build autogen)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <vector>
|
||||
#include "datatypes/annotation.h"
|
||||
#include "error/data.h"
|
||||
#include "lua.h" // IWYU pragma: keep
|
||||
#include "luaapis.h" // IWYU pragma: keep
|
||||
|
||||
struct _EnumData {
|
||||
std::string name;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#include "error/data.h"
|
||||
#include "variant.h"
|
||||
#include <pugixml.hpp>
|
||||
#include "lua.h" // IWYU pragma: keep
|
||||
#include "luaapis.h" // IWYU pragma: keep
|
||||
#include <sstream>
|
||||
|
||||
// null
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <memory>
|
||||
#include <optional>
|
||||
#include "objects/base/instance.h"
|
||||
#include "lua.h" // IWYU pragma: keep
|
||||
#include "luaapis.h" // IWYU pragma: keep
|
||||
#include "objects/base/member.h"
|
||||
#include <pugixml.hpp>
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
#include "signal.h"
|
||||
#include "datatypes/base.h"
|
||||
#include "variant.h"
|
||||
#include "lua.h" // IWYU pragma: keep
|
||||
#include "luaapis.h" // IWYU pragma: keep
|
||||
#include <cstdio>
|
||||
#include <pugixml.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
int script_errhandler(lua_State*); // extern
|
||||
|
||||
SignalSource::SignalSource() : std::shared_ptr<Signal>(std::make_shared<Signal>()) {}
|
||||
SignalSource::~SignalSource() = default;
|
||||
|
||||
|
@ -25,6 +27,8 @@ LuaSignalConnection::LuaSignalConnection(lua_State* L, std::weak_ptr<Signal> par
|
|||
// Save function and current thread so they don't get GC'd
|
||||
function = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
lua_pushthread(L);
|
||||
// For posterity, since I accidentally removed this once, the parent thread must not be
|
||||
// deleted because it may (likely) contain upvalues that the handler references
|
||||
thread = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
|
||||
|
@ -34,40 +38,24 @@ LuaSignalConnection::~LuaSignalConnection() {
|
|||
luaL_unref(state, LUA_REGISTRYINDEX, thread);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void stackdump(lua_State* L) {
|
||||
printf("%d\n", lua_gettop(L));
|
||||
fflush(stdout);
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "tostring");
|
||||
for (int i = lua_gettop(L)-1; i >= 1; i--) {
|
||||
lua_pushvalue(L, -1);
|
||||
lua_pushvalue(L, i);
|
||||
lua_call(L, 1, 1);
|
||||
const char* str = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
printf("%s: %s\n", lua_typename(L, lua_type(L, i)), str);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
printf("\n\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
|
||||
void LuaSignalConnection::Call(std::vector<Variant> args) {
|
||||
lua_State* thread = lua_newthread(state);
|
||||
|
||||
// Push function
|
||||
// Push wrapepr as thread function
|
||||
lua_getfield(thread, LUA_REGISTRYINDEX, "LuaPCallWrapper");
|
||||
|
||||
// Push function as upvalue for wrapper
|
||||
lua_rawgeti(thread, LUA_REGISTRYINDEX, function);
|
||||
|
||||
// Also push our error handler and generate wrapped function
|
||||
lua_pushcfunction(thread, script_errhandler);
|
||||
lua_call(thread, 2, 1);
|
||||
|
||||
for (Variant arg : args) {
|
||||
arg.PushLuaValue(thread);
|
||||
}
|
||||
|
||||
int status = lua_resume(thread, args.size());
|
||||
if (status > LUA_YIELD) {
|
||||
Logger::error(lua_tostring(thread, -1));
|
||||
lua_pop(thread, 1); // Pop return value
|
||||
}
|
||||
lua_resume(thread, args.size());
|
||||
|
||||
lua_pop(state, 1); // Pop thread
|
||||
}
|
||||
|
@ -152,11 +140,7 @@ void Signal::Fire(std::vector<Variant> args) {
|
|||
arg.PushLuaValue(thread);
|
||||
}
|
||||
|
||||
int status = lua_resume(thread, args.size());
|
||||
if (status > LUA_YIELD) {
|
||||
Logger::error(lua_tostring(thread, -1));
|
||||
lua_pop(thread, 1); // Pop return value
|
||||
}
|
||||
lua_resume(thread, args.size());
|
||||
|
||||
// Remove thread from registry
|
||||
luaL_unref(thread, LUA_REGISTRYINDEX, threadId);
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
static std::ofstream logStream;
|
||||
static std::vector<Logger::LogListener> logListeners;
|
||||
static std::vector<Logger::TraceLogListener> traceLogListeners;
|
||||
std::string Logger::currentLogDir = "NULL";
|
||||
|
||||
void Logger::init() {
|
||||
|
@ -28,7 +27,7 @@ void Logger::finish() {
|
|||
logStream.close();
|
||||
}
|
||||
|
||||
void Logger::log(std::string message, Logger::LogLevel logLevel) {
|
||||
void Logger::log(std::string message, Logger::LogLevel logLevel, ScriptSource source) {
|
||||
std::string logLevelStr = logLevel == Logger::LogLevel::INFO ? "INFO" :
|
||||
logLevel == Logger::LogLevel::DEBUG ? "DEBUG" :
|
||||
logLevel == Logger::LogLevel::TRACE ? "TRACE" :
|
||||
|
@ -44,7 +43,7 @@ void Logger::log(std::string message, Logger::LogLevel logLevel) {
|
|||
printf("%s\n", formattedLogLine.c_str());
|
||||
|
||||
for (Logger::LogListener listener : logListeners) {
|
||||
listener(logLevel, message);
|
||||
listener(logLevel, message, source);
|
||||
}
|
||||
|
||||
if (logLevel == Logger::LogLevel::FATAL_ERROR) {
|
||||
|
@ -52,20 +51,6 @@ void Logger::log(std::string message, Logger::LogLevel logLevel) {
|
|||
}
|
||||
}
|
||||
|
||||
void Logger::trace(std::string source, int line, void* userData) {
|
||||
std::string message = "'" + source + "' Line " + std::to_string(line);
|
||||
|
||||
log(message, Logger::LogLevel::TRACE);
|
||||
|
||||
for (Logger::TraceLogListener listener : traceLogListeners) {
|
||||
listener(message, source, line, userData);
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::addLogListener(Logger::LogListener listener) {
|
||||
logListeners.push_back(listener);
|
||||
}
|
||||
|
||||
void Logger::addLogListener(Logger::TraceLogListener listener) {
|
||||
traceLogListeners.push_back(listener);
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Script;
|
||||
|
||||
namespace Logger {
|
||||
enum class LogLevel {
|
||||
INFO,
|
||||
|
@ -14,34 +16,39 @@ namespace Logger {
|
|||
FATAL_ERROR,
|
||||
};
|
||||
|
||||
typedef std::function<void(LogLevel logLevel, std::string message)> LogListener;
|
||||
typedef std::function<void(std::string message, std::string source, int line, void* userData)> TraceLogListener;
|
||||
struct ScriptSource {
|
||||
std::shared_ptr<Script> script;
|
||||
int line;
|
||||
};
|
||||
|
||||
typedef std::function<void(LogLevel logLevel, std::string message, ScriptSource source)> LogListener;
|
||||
|
||||
extern std::string currentLogDir;
|
||||
|
||||
void init();
|
||||
void finish();
|
||||
void addLogListener(LogListener);
|
||||
void addLogListener(TraceLogListener);
|
||||
|
||||
void log(std::string message, LogLevel logLevel);
|
||||
void log(std::string message, LogLevel logLevel, ScriptSource source = {});
|
||||
inline void info(std::string message) { log(message, LogLevel::INFO); }
|
||||
inline void debug(std::string message) { log(message, LogLevel::DEBUG); }
|
||||
inline void warning(std::string message) { log(message, LogLevel::WARNING); }
|
||||
inline void error(std::string message) { log(message, LogLevel::ERROR); }
|
||||
inline void fatalError(std::string message) { log(message, LogLevel::FATAL_ERROR); }
|
||||
inline void trace(std::string message) { log(message, LogLevel::TRACE); };
|
||||
|
||||
inline void traceStart() { log("Stack start", LogLevel::TRACE); }
|
||||
inline void traceEnd() { log("Stack end", LogLevel::TRACE); }
|
||||
void trace(std::string source, int line, void* userData = nullptr);
|
||||
template <typename ...Args>
|
||||
void scriptLogf(std::string format, LogLevel logLevel, ScriptSource source, Args&&... args) {
|
||||
char message[200];
|
||||
sprintf(message, format.c_str(), args...);
|
||||
log(message, logLevel, source);
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
void logf(std::string format, LogLevel logLevel, Args&&... args) {
|
||||
char message[200];
|
||||
sprintf(message, format.c_str(), args...);
|
||||
log(message, logLevel);
|
||||
scriptLogf(format, logLevel, {}, args...);
|
||||
}
|
||||
|
||||
|
||||
template <typename ...Args> inline void infof(std::string format, Args&&... args) { logf(format, LogLevel::INFO, args...); }
|
||||
template <typename ...Args> inline void debugf(std::string format, Args&&... args) { logf(format, LogLevel::DEBUG, args...); }
|
||||
template <typename ...Args> inline void warningf(std::string format, Args&&... args) { logf(format, LogLevel::WARNING, args...); }
|
||||
|
|
77
core/src/lua/instancelib.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
#include "objects/base/instance.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "luaapis.h" // IWYU pragma: keep
|
||||
#include "objects/meta.h"
|
||||
#include <memory>
|
||||
|
||||
static int lib_Instance_index(lua_State*);
|
||||
static int lib_Instance_tostring(lua_State*);
|
||||
static const struct luaL_Reg lib_Instance_metatable [] = {
|
||||
{"__index", lib_Instance_index},
|
||||
{"__tostring", lib_Instance_tostring},
|
||||
{NULL, NULL} /* end of array */
|
||||
};
|
||||
|
||||
static int __lua_impl__Instance__new(lua_State* L) {
|
||||
int n = lua_gettop(L);
|
||||
|
||||
// First argument (className)
|
||||
std::string className = luaL_checkstring(L, 1);
|
||||
|
||||
// [Optional] Second argument (parent)
|
||||
std::shared_ptr<Instance> parent;
|
||||
if (n > 1) {
|
||||
parent = **(std::shared_ptr<Instance>**)luaL_checkudata(L, 2, "__mt_instance");
|
||||
}
|
||||
|
||||
// Look up class name
|
||||
if (INSTANCE_MAP.count(className) == 0)
|
||||
return luaL_error(L, "Attempt to create instance of unknown type '%s'", className.c_str());
|
||||
|
||||
const InstanceType* type = INSTANCE_MAP[className];
|
||||
|
||||
if (type->flags & (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE) || type->constructor == nullptr)
|
||||
return luaL_error(L, "Attempt to create Instance of type '%s', which is not creatable", className.c_str());
|
||||
|
||||
std::shared_ptr<Instance> object = type->constructor();
|
||||
|
||||
if (parent != nullptr)
|
||||
object->SetParent(parent);
|
||||
|
||||
InstanceRef(object).PushLuaValue(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Instance::PushLuaLibrary(lua_State* L) {
|
||||
lua_getglobal(L, "_G");
|
||||
lua_pushstring(L, "Instance");
|
||||
|
||||
lua_newuserdata(L, 0);
|
||||
|
||||
// Create the library's metatable
|
||||
luaL_newmetatable(L, "__mt_lib_Instance");
|
||||
luaL_register(L, NULL, lib_Instance_metatable);
|
||||
lua_setmetatable(L, -2);
|
||||
|
||||
lua_rawset(L, -3);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
|
||||
int lib_Instance_tostring(lua_State* L) {
|
||||
lua_pushstring(L, "Instance");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int lib_Instance_index(lua_State* L) {
|
||||
std::string key(lua_tostring(L, 2));
|
||||
lua_pop(L, 2);
|
||||
|
||||
if (key == "new") {
|
||||
lua_pushcfunction(L, __lua_impl__Instance__new);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return luaL_error(L, "%s is not a valid member of %s\n", key.c_str(), "Instance");
|
||||
}
|
||||
|
|
@ -95,6 +95,7 @@ public:
|
|||
|
||||
// Instance is abstract, so it should not implement GetClass directly
|
||||
virtual const InstanceType* GetClass() = 0;
|
||||
static void PushLuaLibrary(lua_State*); // Defined in lua/instancelib.cpp
|
||||
bool SetParent(std::optional<std::shared_ptr<Instance>> newParent);
|
||||
std::optional<std::shared_ptr<Instance>> GetParent();
|
||||
bool IsParentLocked();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "panic.h"
|
||||
#include <memory>
|
||||
|
||||
Service::Service(const InstanceType* type) : Instance(type){}
|
||||
Service::Service(const InstanceType* type) : Instance(type) {}
|
||||
|
||||
// Fail if parented to non-datamodel, otherwise lock parent
|
||||
void Service::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
|
||||
|
|
4
core/src/objects/hint.cpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
#include "hint.h"
|
||||
|
||||
Hint::Hint(): Message(&TYPE) {}
|
||||
Hint::~Hint() = default;
|
18
core/src/objects/hint.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "objects/annotation.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/message.h"
|
||||
#include <memory>
|
||||
|
||||
// Dims the player's screen and displays some centered text
|
||||
class DEF_INST_(explorer_icon="message") Hint : public Message {
|
||||
AUTOGEN_PREAMBLE
|
||||
|
||||
public:
|
||||
Hint();
|
||||
~Hint();
|
||||
|
||||
static inline std::shared_ptr<Hint> New() { return std::make_shared<Hint>(); };
|
||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Hint>(); };
|
||||
};
|
5
core/src/objects/message.cpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
#include "message.h"
|
||||
|
||||
Message::Message(const InstanceType* type) : Instance(type) {}
|
||||
Message::Message(): Instance(&TYPE) {}
|
||||
Message::~Message() = default;
|
21
core/src/objects/message.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include "objects/annotation.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include <memory>
|
||||
|
||||
// Dims the player's screen and displays some centered text
|
||||
class DEF_INST_(explorer_icon="message") Message : public Instance {
|
||||
AUTOGEN_PREAMBLE
|
||||
|
||||
protected:
|
||||
Message(const InstanceType* type);
|
||||
public:
|
||||
Message();
|
||||
~Message();
|
||||
|
||||
DEF_PROP std::string text;
|
||||
|
||||
static inline std::shared_ptr<Message> New() { return std::make_shared<Message>(); };
|
||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Message>(); };
|
||||
};
|
|
@ -1,9 +1,11 @@
|
|||
#include "meta.h"
|
||||
#include "objects/folder.h"
|
||||
#include "objects/hint.h"
|
||||
#include "objects/joint/jointinstance.h"
|
||||
#include "objects/joint/rotate.h"
|
||||
#include "objects/joint/rotatev.h"
|
||||
#include "objects/joint/weld.h"
|
||||
#include "objects/message.h"
|
||||
#include "objects/service/jointsservice.h"
|
||||
#include "objects/model.h"
|
||||
#include "objects/part.h"
|
||||
|
@ -27,6 +29,8 @@ std::map<std::string, const InstanceType*> INSTANCE_MAP = {
|
|||
{ "JointInstance", &JointInstance::TYPE },
|
||||
{ "Script", &Script::TYPE },
|
||||
{ "Model", &Model::TYPE },
|
||||
{ "Message", &Message::TYPE },
|
||||
{ "Hint", &Hint::TYPE },
|
||||
// { "Folder", &Folder::TYPE },
|
||||
|
||||
// Services
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "script.h"
|
||||
#include "common.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "lauxlib.h"
|
||||
#include "logger.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/base/member.h"
|
||||
|
@ -7,12 +9,11 @@
|
|||
#include "objects/service/workspace.h"
|
||||
#include "objects/datamodel.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "lua.h" // IWYU pragma: keep
|
||||
#include "luaapis.h" // IWYU pragma: keep
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
int script_wait(lua_State*);
|
||||
int script_delay(lua_State*);
|
||||
int script_errhandler(lua_State*);
|
||||
|
||||
Script::Script(): Instance(&TYPE) {
|
||||
source = "print(\"Hello, world!\")";
|
||||
|
@ -25,101 +26,106 @@ void Script::Run() {
|
|||
std::shared_ptr<ScriptContext> scriptContext = dataModel().value()->GetService<ScriptContext>();
|
||||
|
||||
lua_State* L = scriptContext->state;
|
||||
int top = lua_gettop(L);
|
||||
|
||||
// Create thread
|
||||
this->thread = lua_newthread(L);
|
||||
lua_State* Lt = thread;
|
||||
|
||||
// Initialize script globals
|
||||
lua_getglobal(Lt, "_G");
|
||||
// Push wrapper as thread function
|
||||
lua_getfield(Lt, LUA_REGISTRYINDEX, "LuaPCallWrapper");
|
||||
|
||||
InstanceRef(shared_from_this()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "script");
|
||||
|
||||
InstanceRef(dataModel().value()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "game");
|
||||
// Load source code and push onto thread as upvalue for wrapper
|
||||
int status = luaL_loadbuffer(Lt, source.c_str(), source.size(), this->GetFullName().c_str());
|
||||
if (status != LUA_OK) {
|
||||
// Failed to parse/load chunk
|
||||
Logger::error(lua_tostring(Lt, -1));
|
||||
|
||||
InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "workspace");
|
||||
|
||||
lua_pushlightuserdata(Lt, scriptContext.get());
|
||||
lua_pushcclosure(Lt, script_wait, 1);
|
||||
lua_setfield(Lt, -2, "wait");
|
||||
|
||||
lua_pushlightuserdata(Lt, scriptContext.get());
|
||||
lua_pushcclosure(Lt, script_delay, 1);
|
||||
lua_setfield(Lt, -2, "delay");
|
||||
|
||||
lua_pop(Lt, 1); // _G
|
||||
|
||||
// Load source and push onto thread stack as function ptr
|
||||
// luaL_loadstring(Lt, source.c_str());
|
||||
luaL_loadbuffer(Lt, source.c_str(), source.size(), scriptContext->RegisterScriptSource(shared<Script>()).c_str());
|
||||
|
||||
int status = lua_resume(Lt, 0);
|
||||
if (status > LUA_YIELD) {
|
||||
lua_Debug dbg;
|
||||
lua_getstack(Lt, 1, &dbg);
|
||||
lua_getinfo(Lt, "S", &dbg);
|
||||
|
||||
std::weak_ptr<Script> source = scriptContext->GetScriptFromSource(dbg.source);
|
||||
|
||||
std::string errorMessage = lua_tostring(Lt, -1);
|
||||
if (!source.expired())
|
||||
errorMessage = source.lock()->GetFullName() + errorMessage.substr(errorMessage.find(':'));
|
||||
|
||||
Logger::error(errorMessage);
|
||||
lua_pop(Lt, 1); // Pop return value
|
||||
|
||||
Logger::traceStart();
|
||||
|
||||
int stack = 1;
|
||||
while (lua_getstack(Lt, stack++, &dbg)) {
|
||||
lua_getinfo(Lt, "nlSu", &dbg);
|
||||
|
||||
std::weak_ptr<Script> source = scriptContext->GetScriptFromSource(dbg.source);
|
||||
if (source.expired()) {
|
||||
Logger::trace(dbg.source, dbg.currentline);
|
||||
} else {
|
||||
Logger::trace(source.lock()->GetFullName(), dbg.currentline, &source);
|
||||
}
|
||||
}
|
||||
|
||||
Logger::traceEnd();
|
||||
lua_settop(L, top);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize script globals
|
||||
scriptContext->NewEnvironment(Lt); // Pushes envtable, metatable
|
||||
|
||||
// Set script in metatable source
|
||||
InstanceRef(shared_from_this()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "source");
|
||||
|
||||
lua_pop(Lt, 1); // Pop metatable
|
||||
|
||||
// Set script in environment
|
||||
InstanceRef(shared_from_this()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "script");
|
||||
|
||||
lua_setfenv(Lt, -2); // Set env of loaded function
|
||||
|
||||
// Push our error handler and then generate the wrapped function
|
||||
lua_pushcfunction(Lt, script_errhandler);
|
||||
lua_call(Lt, 2, 1);
|
||||
|
||||
// Resume the thread
|
||||
lua_resume(Lt, 0);
|
||||
|
||||
lua_pop(L, 1); // Pop the thread
|
||||
lua_settop(L, top);
|
||||
}
|
||||
|
||||
void Script::Stop() {
|
||||
// TODO:
|
||||
}
|
||||
|
||||
int script_wait(lua_State* L) {
|
||||
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
||||
float secs = lua_gettop(L) == 0 ? 0.03 : std::max(luaL_checknumber(L, 1), 0.03);
|
||||
if (lua_gettop(L) > 0) lua_pop(L, 1); // pop secs
|
||||
static std::shared_ptr<Script> getfsource(lua_State* L, lua_Debug* dbg) {
|
||||
int top = lua_gettop(L);
|
||||
|
||||
scriptContext->PushThreadSleep(L, secs);
|
||||
lua_getinfo(L, "f", dbg);
|
||||
lua_getfenv(L, -1); // Get fenv of stack pos
|
||||
if (lua_isnil(L, -1)) { // No env could be found
|
||||
lua_settop(L, top);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Yield
|
||||
return lua_yield(L, 0);
|
||||
// Get source from metatable
|
||||
lua_getmetatable(L, -1);
|
||||
lua_getfield(L, -1, "source");
|
||||
|
||||
auto result = InstanceRef::FromLuaValue(L, -1);
|
||||
if (!result) {
|
||||
lua_settop(L, top);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
lua_settop(L, top);
|
||||
|
||||
std::shared_ptr<Instance> ref = result.expect().get<InstanceRef>();
|
||||
if (!ref->IsA<Script>()) return nullptr;
|
||||
|
||||
return ref->CastTo<Script>().expect();
|
||||
}
|
||||
|
||||
int script_delay(lua_State* L) {
|
||||
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
||||
float secs = std::max(luaL_checknumber(L, 1), 0.03);
|
||||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||
int script_errhandler(lua_State* L) {
|
||||
std::string errorMessage = lua_tostring(L, -1);
|
||||
Logger::error(errorMessage);
|
||||
|
||||
lua_State* Lt = lua_newthread(L); // Create a new thread
|
||||
// I think this is memory abuse??
|
||||
// Wouldn't popping the thread in this case make it eligible for garbage collection?
|
||||
lua_pop(L, 1); // pop the newly created thread so that xmove moves func instead of it into itself
|
||||
lua_xmove(L, Lt, 1); // move func
|
||||
lua_pop(L, 1); // pop secs
|
||||
// Traceback
|
||||
|
||||
// Schedule next run
|
||||
scriptContext->PushThreadSleep(Lt, secs);
|
||||
Logger::trace("Stack start");
|
||||
|
||||
lua_Debug dbg;
|
||||
int stack = 1;
|
||||
while (lua_getstack(L, stack++, &dbg)) {
|
||||
lua_getinfo(L, "nlSu", &dbg);
|
||||
// Ignore C frames and internal wrappers
|
||||
if (strcmp(dbg.what, "C") == 0 || strcmp(dbg.source, "=PCALL_WRAPPER") == 0)
|
||||
continue;
|
||||
|
||||
// Find script source
|
||||
std::shared_ptr<Script> source = getfsource(L, &dbg);
|
||||
|
||||
Logger::scriptLogf("'%s', Line %d", Logger::LogLevel::TRACE, {source, dbg.currentline}, dbg.source, dbg.currentline);
|
||||
}
|
||||
|
||||
Logger::trace("Stack end");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,22 +1,30 @@
|
|||
#include "scriptcontext.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/color3.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "logger.h"
|
||||
#include "objects/datamodel.h"
|
||||
#include "objects/service/workspace.h"
|
||||
#include "timeutil.h"
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include "lua.h" // IWYU pragma: keep
|
||||
#include "luaapis.h" // IWYU pragma: keep
|
||||
|
||||
const char* WRAPPER_SRC = "local func, errhandler = ... return function(...) local args = {...} xpcall(function() func(unpack(args)) end, errhandler) end";
|
||||
|
||||
int g_wait(lua_State*);
|
||||
int g_delay(lua_State*);
|
||||
static int g_print(lua_State*);
|
||||
static int g_require(lua_State*);
|
||||
static const struct luaL_Reg luaglobals [] = {
|
||||
static const luaL_Reg luaglobals [] = {
|
||||
{"print", g_print},
|
||||
{"require", g_require},
|
||||
{NULL, NULL} /* end of array */
|
||||
};
|
||||
|
||||
std::string unsafe_globals[] = {
|
||||
// Todo implement our own "safe" setfenv/getfenv
|
||||
"loadfile", "loadstring", "load", "dofile", "getfenv", "setfenv"
|
||||
};
|
||||
|
||||
|
@ -46,6 +54,30 @@ void ScriptContext::InitService() {
|
|||
Vector3::PushLuaLibrary(state);
|
||||
CFrame::PushLuaLibrary(state);
|
||||
Color3::PushLuaLibrary(state);
|
||||
Instance::PushLuaLibrary(state);
|
||||
|
||||
// Add other globals
|
||||
lua_getglobal(state, "_G");
|
||||
|
||||
InstanceRef(dataModel().value()).PushLuaValue(state);
|
||||
lua_setfield(state, -2, "game");
|
||||
|
||||
InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(state);
|
||||
lua_setfield(state, -2, "workspace");
|
||||
|
||||
lua_pushlightuserdata(state, this);
|
||||
lua_pushcclosure(state, g_wait, 1);
|
||||
lua_setfield(state, -2, "wait");
|
||||
|
||||
lua_pushlightuserdata(state, this);
|
||||
lua_pushcclosure(state, g_delay, 1);
|
||||
lua_setfield(state, -2, "delay");
|
||||
|
||||
lua_pop(state, 1); // _G
|
||||
|
||||
// Add wrapper function
|
||||
luaL_loadbuffer(state, WRAPPER_SRC, strlen(WRAPPER_SRC), "=PCALL_WRAPPER");
|
||||
lua_setfield(state, LUA_REGISTRYINDEX, "LuaPCallWrapper");
|
||||
|
||||
// TODO: custom os library
|
||||
|
||||
|
@ -105,11 +137,7 @@ void ScriptContext::RunSleepingThreads() {
|
|||
// Time args
|
||||
lua_pushnumber(sleep.thread, float(tu_clock_micros() - sleep.timeYieldedWhen) / 1'000'000);
|
||||
lua_pushnumber(sleep.thread, float(tu_clock_micros()) / 1'000'000);
|
||||
int status = lua_resume(sleep.thread, 2);
|
||||
if (status > LUA_YIELD) {
|
||||
Logger::error(lua_tostring(sleep.thread, -1));
|
||||
lua_pop(sleep.thread, 1); // Pop return value
|
||||
}
|
||||
lua_resume(sleep.thread, 2);
|
||||
|
||||
// Remove thread
|
||||
deleted = true;
|
||||
|
@ -134,21 +162,25 @@ void ScriptContext::RunSleepingThreads() {
|
|||
schedTime = tu_clock_micros() - startTime;
|
||||
}
|
||||
|
||||
std::string ScriptContext::RegisterScriptSource(std::shared_ptr<Script> script) {
|
||||
// If it has already been registered, reference it here
|
||||
for (auto& [id, script2] : scriptSources) {
|
||||
if (!script2.expired() && script2.lock() == script)
|
||||
return "=f" + std::to_string(id);
|
||||
}
|
||||
void ScriptContext::NewEnvironment(lua_State* L) {
|
||||
lua_newtable(L); // Env table
|
||||
lua_newtable(L); // Metatable
|
||||
|
||||
int id = lastScriptSourceId++;
|
||||
scriptSources[id] = script;
|
||||
return "=f" + std::to_string(id);
|
||||
}
|
||||
// Push __index
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
lua_setfield(L, -2, "__index");
|
||||
|
||||
std::weak_ptr<Script> ScriptContext::GetScriptFromSource(std::string source) {
|
||||
int id = std::stoi(source.c_str() + 2);
|
||||
return scriptSources[id];
|
||||
// Push __metatable
|
||||
lua_pushstring(L, "metatable is locked");
|
||||
lua_setfield(L, -2, "__metatable");
|
||||
|
||||
// Copy metatable and set the env table's metatable
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setmetatable(L, -3);
|
||||
|
||||
// Remainder on stack:
|
||||
// 1. Env table
|
||||
// 2. Metatable
|
||||
}
|
||||
|
||||
// https://www.lua.org/source/5.1/lbaselib.c.html
|
||||
|
@ -181,4 +213,33 @@ static int g_require(lua_State* L) {
|
|||
if (nargs < 1) return luaL_error(L, "expected argument module");
|
||||
|
||||
return luaL_error(L, "require is not yet implemented");
|
||||
}
|
||||
|
||||
int g_wait(lua_State* L) {
|
||||
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
||||
float secs = lua_gettop(L) == 0 ? 0.03 : std::max(luaL_checknumber(L, 1), 0.03);
|
||||
if (lua_gettop(L) > 0) lua_pop(L, 1); // pop secs
|
||||
|
||||
scriptContext->PushThreadSleep(L, secs);
|
||||
|
||||
// Yield
|
||||
return lua_yield(L, 0);
|
||||
}
|
||||
|
||||
int g_delay(lua_State* L) {
|
||||
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
||||
float secs = std::max(luaL_checknumber(L, 1), 0.03);
|
||||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||
|
||||
lua_State* Lt = lua_newthread(L); // Create a new thread
|
||||
// I think this is memory abuse??
|
||||
// Wouldn't popping the thread in this case make it eligible for garbage collection?
|
||||
lua_pop(L, 1); // pop the newly created thread so that xmove moves func instead of it into itself
|
||||
lua_xmove(L, Lt, 1); // move func
|
||||
lua_pop(L, 1); // pop secs
|
||||
|
||||
// Schedule next run
|
||||
scriptContext->PushThreadSleep(Lt, secs);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include "objects/annotation.h"
|
||||
#include "objects/base/service.h"
|
||||
#include "lua.h" // IWYU pragma: keep
|
||||
#include "luaapis.h" // IWYU pragma: keep
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
@ -19,7 +19,6 @@ class DEF_INST_SERVICE ScriptContext : public Service {
|
|||
AUTOGEN_PREAMBLE
|
||||
|
||||
std::vector<SleepingThread> sleepingThreads;
|
||||
std::map<int, std::weak_ptr<Script>> scriptSources;
|
||||
int lastScriptSourceId = 0;
|
||||
protected:
|
||||
void InitService() override;
|
||||
|
@ -32,8 +31,9 @@ public:
|
|||
lua_State* state;
|
||||
void PushThreadSleep(lua_State* thread, float delay);
|
||||
void RunSleepingThreads();
|
||||
std::string RegisterScriptSource(std::shared_ptr<Script>);
|
||||
std::weak_ptr<Script> GetScriptFromSource(std::string source);
|
||||
|
||||
// Generates an environment with a metatable and pushes it both the env table and metatable in order onto the stack
|
||||
void NewEnvironment(lua_State* state);
|
||||
|
||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<ScriptContext>(); };
|
||||
};
|
|
@ -7,19 +7,21 @@
|
|||
#include <string>
|
||||
|
||||
extern int viewportWidth, viewportHeight;
|
||||
extern Texture* fontTexture;
|
||||
extern Shader* fontShader;
|
||||
extern Texture* debugFontTexture;
|
||||
extern Shader* debugFontShader;
|
||||
extern Shader* identityShader;
|
||||
|
||||
void drawChar(char c, int x, int y, float scale=1.f) {
|
||||
fontShader->use();
|
||||
fontTexture->activate(1);
|
||||
fontShader->set("fontTex", 1);
|
||||
void drawRect(int x, int y, int width, int height, glm::vec4 color);
|
||||
|
||||
fontShader->set("charIndex", (int)c);
|
||||
void drawChar(char c, int x, int y, float scale=1.f) {
|
||||
debugFontShader->use();
|
||||
debugFontTexture->activate(1);
|
||||
debugFontShader->set("fontTex", 1);
|
||||
|
||||
debugFontShader->set("charIndex", (int)c);
|
||||
|
||||
// https://stackoverflow.com/a/10631263
|
||||
int tex = fontShader->getAttribute("aTexCoord");
|
||||
int tex = debugFontShader->getAttribute("aTexCoord");
|
||||
|
||||
y = viewportHeight - y - 16*scale;
|
||||
float x0 = float(x)/viewportWidth, y0 = float(y)/viewportHeight, x1 = ((float)x + 8*scale)/viewportWidth, y1 = ((float)y + 16*scale)/viewportHeight;
|
||||
|
@ -41,22 +43,6 @@ void drawString(std::string str, int x, int y, float scale=1.f) {
|
|||
}
|
||||
}
|
||||
|
||||
void drawRect(int x, int y, int w, int h, glm::vec4 color) {
|
||||
identityShader->use();
|
||||
identityShader->set("aColor", color);
|
||||
|
||||
float x0 = 2*float(x)/viewportWidth-1, y0 = 2*float(y)/viewportHeight-1, x1 = 2*float(x + w)/viewportWidth-1, y1 = 2*float(y + h)/viewportHeight-1;
|
||||
float tmp;
|
||||
tmp = -y0, y0 = -y1, y1 = tmp;
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
glVertex3f(x0, y0, 0);
|
||||
glVertex3f(x1, y0, 0);
|
||||
glVertex3f(x1, y1, 0);
|
||||
glVertex3f(x0, y1, 0);
|
||||
glEnd();
|
||||
}
|
||||
|
||||
static tu_time_t lastTime;
|
||||
extern tu_time_t renderTime;
|
||||
extern tu_time_t physTime;
|
||||
|
|
208
core/src/rendering/font.cpp
Normal file
|
@ -0,0 +1,208 @@
|
|||
#include "font.h"
|
||||
#include "logger.h"
|
||||
#include "panic.h"
|
||||
#include "rendering/shader.h"
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <GL/gl.h>
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
#include <memory>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_STROKER_H
|
||||
|
||||
// https://learnopengl.com/In-Practice/Text-Rendering
|
||||
|
||||
FT_Library freetype;
|
||||
Shader* fontShader;
|
||||
|
||||
extern int viewportWidth, viewportHeight;
|
||||
|
||||
unsigned int textVAO, textVBO;
|
||||
|
||||
void fontInit() {
|
||||
if (FT_Error err = FT_Init_FreeType(&freetype)) {
|
||||
Logger::fatalErrorf("Failed to initialize Freetype: [%d]", err);
|
||||
panic();
|
||||
}
|
||||
|
||||
fontShader = new Shader("assets/shaders/font.vs", "assets/shaders/font.fs");
|
||||
|
||||
// Set up buffer
|
||||
glGenVertexArrays(1, &textVAO);
|
||||
glBindVertexArray(textVAO);
|
||||
|
||||
glGenBuffers(1, &textVBO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, textVBO);
|
||||
|
||||
// Dynamic, because we update the vertices often V~~~~~~~~~~~~~~
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void fontFinish() {
|
||||
if (FT_Error err = FT_Done_FreeType(freetype)) {
|
||||
Logger::fatalErrorf("Failed to free Freetype: [%d]", err);
|
||||
panic();
|
||||
}
|
||||
}
|
||||
|
||||
static void loadCharTexture(FT_Face& face, FT_BitmapGlyph& glyph_bitmap, Character& character) {
|
||||
// Generate texture
|
||||
unsigned int texture;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RED,
|
||||
glyph_bitmap->bitmap.width,
|
||||
glyph_bitmap->bitmap.rows,
|
||||
0,
|
||||
GL_RED,
|
||||
GL_UNSIGNED_BYTE,
|
||||
glyph_bitmap->bitmap.buffer
|
||||
);
|
||||
// set texture options
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
// now store character for later use
|
||||
character.ID = texture;
|
||||
character.size = glm::ivec2(glyph_bitmap->bitmap.width, glyph_bitmap->bitmap.rows);
|
||||
character.bearing = glm::ivec2(glyph_bitmap->left, glyph_bitmap->top);
|
||||
character.advance = (unsigned int)face->glyph->advance.x;
|
||||
}
|
||||
|
||||
std::shared_ptr<Font> loadFont(std::string fontName) {
|
||||
std::string fontPath = "assets/font/" + fontName;
|
||||
|
||||
FT_Face face;
|
||||
if (FT_Error err = FT_New_Face(freetype, fontPath.c_str(), 0, &face)) {
|
||||
Logger::fatalErrorf("Failed to create font '%s': [%d]", fontName.c_str(), err);
|
||||
panic();
|
||||
}
|
||||
|
||||
std::shared_ptr<Font> font = std::make_shared<Font>();
|
||||
|
||||
FT_Set_Pixel_Sizes(face, 0, 16);
|
||||
font->height = face->size->metrics.y_ppem;
|
||||
|
||||
FT_Stroker stroker;
|
||||
FT_Stroker_New(freetype, &stroker);
|
||||
FT_Stroker_Set(stroker, 2 * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
|
||||
|
||||
// Load each glyph
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
for (unsigned char c = 0; c < 128; c++) {
|
||||
// load character glyph
|
||||
if (FT_Error err = FT_Load_Char(face, c, FT_LOAD_DEFAULT)) {
|
||||
Logger::errorf("Failed to load glyph %d in font '%s': [%d]", c, fontName.c_str(), err);
|
||||
continue;
|
||||
}
|
||||
|
||||
FT_Glyph glyph;
|
||||
FT_BitmapGlyph glyph_bitmap;
|
||||
|
||||
Character character;
|
||||
|
||||
// Render base
|
||||
FT_Get_Glyph(face->glyph, &glyph);
|
||||
FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, nullptr, true);
|
||||
glyph_bitmap = (FT_BitmapGlyph)glyph;
|
||||
|
||||
loadCharTexture(face, glyph_bitmap, character);
|
||||
font->characters[c] = character;
|
||||
FT_Done_Glyph(glyph);
|
||||
// TODO: Find out how to clear FT_BitmapGlyph... I cant import FT_Bitmap_Done for some reason
|
||||
|
||||
// Render stroked
|
||||
// https://stackoverflow.com/a/28078293/16255372
|
||||
FT_Get_Glyph(face->glyph, &glyph);
|
||||
FT_Glyph_StrokeBorder(&glyph, stroker, false, true);
|
||||
FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, nullptr, true);
|
||||
glyph_bitmap = reinterpret_cast<FT_BitmapGlyph>(glyph);
|
||||
|
||||
loadCharTexture(face, glyph_bitmap, character);
|
||||
font->strokeCharacters[c] = character;
|
||||
FT_Done_Glyph(glyph);
|
||||
}
|
||||
|
||||
FT_Stroker_Done(stroker);
|
||||
FT_Done_Face(face);
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
void drawText(std::shared_ptr<Font> font, std::string text, float x, float y, float scale, glm::vec3 color, bool drawStroke) {
|
||||
// activate corresponding render state
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
fontShader->use();
|
||||
fontShader->set("textColor", color);
|
||||
fontShader->set("text", 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
glm::mat4 projection = glm::ortho(0.0f, (float)viewportWidth, 0.0f, (float)viewportHeight);
|
||||
fontShader->set("projection", projection);
|
||||
|
||||
// This is not in the learnopengl guide but it is VERY important
|
||||
// I'm surprised I missed it but honestly... not so much. I'm an idiot
|
||||
glBindVertexArray(textVAO);
|
||||
|
||||
// iterate through all characters
|
||||
for (size_t i = 0; i < text.size(); i++) {
|
||||
unsigned char c = text[i];
|
||||
Character ch = drawStroke ? font->strokeCharacters[c] : font->characters[c];
|
||||
|
||||
float xpos = x + ch.bearing.x * scale;
|
||||
float ypos = viewportHeight - y - font->height - (ch.size.y - ch.bearing.y) * scale;
|
||||
|
||||
float w = ch.size.x * scale;
|
||||
float h = ch.size.y * scale;
|
||||
// render glyph texture over quad
|
||||
glBindTexture(GL_TEXTURE_2D, ch.ID);
|
||||
|
||||
float vertices[6][4] = {
|
||||
{ xpos, ypos + h, 0.0f, 0.0f },
|
||||
{ xpos, ypos, 0.0f, 1.0f },
|
||||
{ xpos + w, ypos, 1.0f, 1.0f },
|
||||
|
||||
{ xpos, ypos + h, 0.0f, 0.0f },
|
||||
{ xpos + w, ypos, 1.0f, 1.0f },
|
||||
{ xpos + w, ypos + h, 1.0f, 0.0f }
|
||||
};
|
||||
// render glyph texture over quad
|
||||
glBindTexture(GL_TEXTURE_2D, ch.ID);
|
||||
// update content of VBO memory
|
||||
glBindBuffer(GL_ARRAY_BUFFER, textVBO);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
// render quad
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
// now advance cursors for next glyph (note that advance is number of 1/64 pixels)
|
||||
x += (ch.advance >> 6) * scale; // bitshift by 6 to get value in pixels (2^6 = 64)
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
float calcTextWidth(std::shared_ptr<Font> font, std::string text, float scale) {
|
||||
float x = 0;
|
||||
// iterate through all characters
|
||||
for (size_t i = 0; i < text.size(); i++) {
|
||||
unsigned char c = text[i];
|
||||
Character ch = font->characters[c];
|
||||
|
||||
x += (ch.advance >> 6) * scale;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
26
core/src/rendering/font.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
// https://learnopengl.com/In-Practice/Text-Rendering
|
||||
|
||||
struct Character {
|
||||
unsigned int ID; // ID handle of the glyph texture
|
||||
glm::ivec2 size; // Size of glyph
|
||||
glm::ivec2 bearing; // Offset from baseline to left/top of glyph
|
||||
unsigned int advance; // Offset to advance to next glyph
|
||||
};
|
||||
|
||||
struct Font {
|
||||
unsigned int height;
|
||||
Character characters[128];
|
||||
Character strokeCharacters[128];
|
||||
};
|
||||
|
||||
void fontInit();
|
||||
void fontFinish();
|
||||
std::shared_ptr<Font> loadFont(std::string fontName);
|
||||
void drawText(std::shared_ptr<Font> font, std::string text, float x, float y, float scale=1.f, glm::vec3 color = glm::vec3(1,1,1), bool drawStroke = false);
|
||||
float calcTextWidth(std::shared_ptr<Font> font, std::string text, float scale = 1.f);
|
|
@ -1,5 +1,4 @@
|
|||
#include <GL/glew.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <GL/gl.h>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
@ -21,8 +20,11 @@
|
|||
#include "datatypes/vector.h"
|
||||
#include "handles.h"
|
||||
#include "math_helper.h"
|
||||
#include "objects/hint.h"
|
||||
#include "objects/message.h"
|
||||
#include "objects/service/selection.h"
|
||||
#include "partassembly.h"
|
||||
#include "rendering/font.h"
|
||||
#include "rendering/mesh2d.h"
|
||||
#include "rendering/texture.h"
|
||||
#include "rendering/torus.h"
|
||||
|
@ -46,23 +48,26 @@ Shader* identityShader = NULL;
|
|||
Shader* ghostShader = NULL;
|
||||
Shader* wireframeShader = NULL;
|
||||
Shader* outlineShader = NULL;
|
||||
Shader* fontShader = NULL;
|
||||
Shader* debugFontShader = NULL;
|
||||
Shader* generic2dShader = NULL;
|
||||
extern Camera camera;
|
||||
Skybox* skyboxTexture = NULL;
|
||||
Texture3D* studsTexture = NULL;
|
||||
Texture* fontTexture = NULL;
|
||||
Texture* debugFontTexture = NULL;
|
||||
Mesh2D* rect2DMesh = NULL;
|
||||
|
||||
std::shared_ptr<Font> sansSerif;
|
||||
|
||||
bool debugRendererEnabled = false;
|
||||
bool wireframeRendering = false;
|
||||
|
||||
int viewportWidth, viewportHeight;
|
||||
|
||||
void renderDebugInfo();
|
||||
void drawRect(int x, int y, int width, int height, glm::vec3 color);
|
||||
void drawRect(int x, int y, int width, int height, glm::vec4 color);
|
||||
inline void drawRect(int x, int y, int width, int height, glm::vec3 color) { return drawRect(x, y, width, height, glm::vec4(color, 1)); };
|
||||
|
||||
void renderInit(GLFWwindow* window, int width, int height) {
|
||||
void renderInit(int width, int height) {
|
||||
viewportWidth = width, viewportHeight = height;
|
||||
glViewport(0, 0, width, height);
|
||||
|
||||
|
@ -74,7 +79,7 @@ void renderInit(GLFWwindow* window, int width, int height) {
|
|||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
fontTexture = new Texture("assets/textures/debugfnt.bmp", GL_RGB);
|
||||
debugFontTexture = new Texture("assets/textures/debugfnt.bmp", GL_RGB);
|
||||
|
||||
skyboxTexture = new Skybox({
|
||||
"assets/textures/skybox/null_plainsky512_lf.jpg",
|
||||
|
@ -95,7 +100,7 @@ void renderInit(GLFWwindow* window, int width, int height) {
|
|||
ghostShader = new Shader("assets/shaders/ghost.vs", "assets/shaders/ghost.fs");
|
||||
wireframeShader = new Shader("assets/shaders/wireframe.vs", "assets/shaders/wireframe.fs");
|
||||
outlineShader = new Shader("assets/shaders/outline.vs", "assets/shaders/outline.fs");
|
||||
fontShader = new Shader("assets/shaders/font.vs", "assets/shaders/font.fs");
|
||||
debugFontShader = new Shader("assets/shaders/debug/debugfont.vs", "assets/shaders/debug/debugfont.fs");
|
||||
generic2dShader = new Shader("assets/shaders/generic2d.vs", "assets/shaders/generic2d.fs");
|
||||
|
||||
// Create mesh for 2d rectangle
|
||||
|
@ -110,6 +115,10 @@ void renderInit(GLFWwindow* window, int width, int height) {
|
|||
};
|
||||
|
||||
rect2DMesh = new Mesh2D(6, rectVerts);
|
||||
|
||||
// Initialize fonts
|
||||
fontInit();
|
||||
sansSerif = loadFont("LiberationSans-Regular.ttf");
|
||||
}
|
||||
|
||||
void renderParts() {
|
||||
|
@ -356,7 +365,6 @@ void renderHandles() {
|
|||
glm::vec4 screenPos = projection * view * glm::vec4((glm::vec3)cframe.Position(), 1.0f);
|
||||
screenPos /= screenPos.w;
|
||||
screenPos += 1; screenPos /= 2; screenPos.y = 1 - screenPos.y; screenPos *= glm::vec4(glm::vec2(viewportWidth, viewportHeight), 1, 1);
|
||||
printVec((glm::vec3)screenPos);
|
||||
|
||||
drawRect(screenPos.x - 3, screenPos.y - 3, 6, 6, glm::vec3(0, 1, 1));
|
||||
}
|
||||
|
@ -572,6 +580,7 @@ void renderRotationArcs() {
|
|||
handleShader->set("viewPos", camera.cameraPos);
|
||||
|
||||
PartAssembly assembly = PartAssembly::FromSelection(gDataModel->GetService<Selection>());
|
||||
if (assembly.size() == Vector3::ZERO) return; // No parts are selected
|
||||
|
||||
for (HandleFace face : HandleFace::Faces) {
|
||||
if (glm::any(glm::lessThan(face.normal, glm::vec3(0)))) continue;
|
||||
|
@ -643,8 +652,35 @@ void addDebugRenderCFrame(CFrame frame, Color3 color) {
|
|||
DEBUG_CFRAMES.push_back(std::make_pair(frame, color));
|
||||
}
|
||||
|
||||
void renderMessages() {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
// glEnable(GL_DEPTH_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
// glEnable(GL_BLEND);
|
||||
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
|
||||
if (!it->IsA<Message>()) continue;
|
||||
std::shared_ptr<Message> message = it->CastTo<Message>().expect();
|
||||
|
||||
float textWidth = calcTextWidth(sansSerif, message->text);
|
||||
|
||||
// Render hint
|
||||
if (message->GetClass() == &Hint::TYPE) {
|
||||
drawRect(0, 0, viewportWidth, 20, glm::vec4(0,0,0,1));
|
||||
drawText(sansSerif, message->text, (viewportWidth - textWidth) / 2, 0);
|
||||
} else {
|
||||
// Don't draw if text is empty
|
||||
if (message->text == "") continue;
|
||||
|
||||
drawRect(0, 0, viewportWidth, viewportHeight, glm::vec4(0.5));
|
||||
drawText(sansSerif, message->text, ((float)viewportWidth - textWidth) / 2, ((float)viewportHeight - sansSerif->height) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tu_time_t renderTime;
|
||||
void render(GLFWwindow* window) {
|
||||
void render() {
|
||||
tu_time_t startTime = tu_clock_micros();
|
||||
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
@ -662,12 +698,14 @@ void render(GLFWwindow* window) {
|
|||
renderWireframe();
|
||||
if (debugRendererEnabled)
|
||||
renderDebugInfo();
|
||||
renderMessages();
|
||||
// TODO: Make this a debug flag
|
||||
// renderAABB();
|
||||
|
||||
renderTime = tu_clock_micros() - startTime;
|
||||
}
|
||||
|
||||
void drawRect(int x, int y, int width, int height, glm::vec3 color) {
|
||||
void drawRect(int x, int y, int width, int height, glm::vec4 color) {
|
||||
// GL_CULL_FACE has to be disabled as we are flipping the order of the vertices here, besides we don't really care about it
|
||||
glDisable(GL_CULL_FACE);
|
||||
glm::mat4 model(1.0f); // Same applies to this VV
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
#pragma once
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
extern bool wireframeRendering;
|
||||
|
||||
class CFrame;
|
||||
class Color3;
|
||||
|
||||
void renderInit(GLFWwindow* window, int width, int height);
|
||||
void render(GLFWwindow* window);
|
||||
void renderInit(int width, int height);
|
||||
void render();
|
||||
void setViewport(int width, int height);
|
||||
void addDebugRenderCFrame(CFrame);
|
||||
void addDebugRenderCFrame(CFrame, Color3);
|
||||
|
|
|
@ -7,7 +7,42 @@
|
|||
|
||||
struct vec { float x; float y; float z; };
|
||||
|
||||
void genTorusPoint(float outerRadius, float innerRadius, float tubeSides, float ringSides, int tube, int ring) {
|
||||
unsigned int torusVAO, torusVBO;
|
||||
int lastSize = 0;
|
||||
void initTorus(int tubeSides, int ringSides) {
|
||||
// Free existing buffer
|
||||
if (torusVAO != 0)
|
||||
glDeleteVertexArrays(1, &torusVAO);
|
||||
if (torusVBO != 0)
|
||||
glDeleteBuffers(1, &torusVBO);
|
||||
|
||||
lastSize = tubeSides * ringSides;
|
||||
|
||||
// Set up buffer
|
||||
glGenVertexArrays(1, &torusVAO);
|
||||
glBindVertexArray(torusVAO);
|
||||
|
||||
glGenBuffers(1, &torusVBO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, torusVBO);
|
||||
|
||||
// Dynamic, because we update the vertices often V~~~~~~~~~~~~~~
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 8 * 6 * tubeSides * ringSides, NULL, GL_DYNAMIC_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(0 * sizeof(float)));
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void genTorusPoint(float* vertex, float outerRadius, float innerRadius, float tubeSides, float ringSides, int tube, int ring) {
|
||||
float angle = float(tube) / tubeSides * 2 * PI;
|
||||
float xL = innerRadius * cos(angle);
|
||||
float yL = innerRadius * sin(angle);
|
||||
|
@ -17,31 +52,41 @@ void genTorusPoint(float outerRadius, float innerRadius, float tubeSides, float
|
|||
float y = (outerRadius + xL) * sin(outerAngle);
|
||||
float z = yL;
|
||||
|
||||
// return vec { x, y, z };
|
||||
glVertex3f(x, y, z);
|
||||
vertex[0] = x; vertex[1] = y; vertex[2] = z;
|
||||
// TODO: Add normals and tex coords
|
||||
}
|
||||
|
||||
// made by yours truly
|
||||
void genTorus(float outerRadius, float innerRadius, int tubeSides, int ringSides) {
|
||||
glBegin(GL_TRIANGLES);
|
||||
// Automatically generate VBO ad-hoc
|
||||
if (lastSize != tubeSides * ringSides)
|
||||
initTorus(tubeSides, ringSides);
|
||||
|
||||
float* vertices = new float[(tubeSides * ringSides * 6) * 8];
|
||||
|
||||
int vi = 0;
|
||||
for (int i = 0; i < tubeSides; i++) {
|
||||
float tube = float(i) / tubeSides;
|
||||
for (int j = 0; j < ringSides; j++) {
|
||||
float ring = float(j) / ringSides;
|
||||
|
||||
int in = (i+1) % tubeSides;
|
||||
int jn = (j+1) % tubeSides;
|
||||
|
||||
genTorusPoint(outerRadius, innerRadius, tubeSides, ringSides, i, j);
|
||||
genTorusPoint(outerRadius, innerRadius, tubeSides, ringSides, in, j);
|
||||
genTorusPoint(outerRadius, innerRadius, tubeSides, ringSides, in, jn);
|
||||
genTorusPoint(&vertices[8*vi++], outerRadius, innerRadius, tubeSides, ringSides, i, j);
|
||||
genTorusPoint(&vertices[8*vi++], outerRadius, innerRadius, tubeSides, ringSides, in, j);
|
||||
genTorusPoint(&vertices[8*vi++], outerRadius, innerRadius, tubeSides, ringSides, in, jn);
|
||||
|
||||
genTorusPoint(outerRadius, innerRadius, tubeSides, ringSides, in, jn);
|
||||
genTorusPoint(outerRadius, innerRadius, tubeSides, ringSides, i, jn);
|
||||
genTorusPoint(outerRadius, innerRadius, tubeSides, ringSides, i, j);
|
||||
genTorusPoint(&vertices[8*vi++], outerRadius, innerRadius, tubeSides, ringSides, in, jn);
|
||||
genTorusPoint(&vertices[8*vi++], outerRadius, innerRadius, tubeSides, ringSides, i, jn);
|
||||
genTorusPoint(&vertices[8*vi++], outerRadius, innerRadius, tubeSides, ringSides, i, j);
|
||||
}
|
||||
}
|
||||
|
||||
glEnd();
|
||||
// Bind new data
|
||||
glBindBuffer(GL_ARRAY_BUFFER, torusVBO);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 8 * 6 * tubeSides * ringSides, vertices);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
// Draw
|
||||
glBindVertexArray(torusVAO);
|
||||
glDrawArrays(GL_TRIANGLES, 0, tubeSides * ringSides * 6);
|
||||
}
|
3
deps.txt
|
@ -8,4 +8,5 @@ qt6
|
|||
reactphysics3d
|
||||
pugixml
|
||||
luajit
|
||||
qscintilla
|
||||
qscintilla
|
||||
freetype2
|
|
@ -38,6 +38,8 @@ set(PROJECT_SOURCES
|
|||
panes/outputtextview.cpp
|
||||
script/scriptdocument.h
|
||||
script/scriptdocument.cpp
|
||||
script/commandedit.h
|
||||
script/commandedit.cpp
|
||||
aboutdialog.ui
|
||||
aboutdialog.h
|
||||
aboutdialog.cpp
|
||||
|
@ -98,14 +100,6 @@ add_custom_command(
|
|||
if (WIN32)
|
||||
#include("${QT_DEPLOY_SUPPORT}")
|
||||
|
||||
# TODO: Add other translations
|
||||
add_custom_command(
|
||||
TARGET editor POST_BUILD
|
||||
COMMAND ${WINDEPLOYQT_EXECUTABLE} $<TARGET_FILE:editor> --translations en --no-compiler-runtime --no-opengl-sw --no-system-d3d-compiler --plugindir $<TARGET_FILE_DIR:editor>/qtplugins
|
||||
)
|
||||
# No sense adding opengl-sw given that hardware acceleration is necessary, anyway
|
||||
# Also don't want to clutter with plugins, add only needed ones
|
||||
|
||||
# Copy over QScintilla DLLs
|
||||
# TODO: Use a better approach?
|
||||
add_custom_command(
|
||||
|
@ -113,6 +107,15 @@ if (WIN32)
|
|||
COMMAND ${CMAKE_COMMAND} -E copy ${QSCINTILLA_DLLS} $<TARGET_FILE_DIR:editor>
|
||||
)
|
||||
|
||||
# TODO: Add other translations
|
||||
add_custom_command(
|
||||
TARGET editor POST_BUILD
|
||||
COMMAND ${WINDEPLOYQT_EXECUTABLE} $<TARGET_FILE:editor> ${QSCINTILLA_DLLS} --dir $<TARGET_FILE_DIR:editor> --translations en --no-compiler-runtime --no-opengl-sw --no-system-d3d-compiler --plugindir $<TARGET_FILE_DIR:editor>/qtplugins
|
||||
)
|
||||
# No sense adding opengl-sw given that hardware acceleration is necessary, anyway
|
||||
# Also don't want to clutter with plugins, add only needed ones
|
||||
|
||||
|
||||
# Copy qt.conf to override default plugins location
|
||||
add_custom_command(
|
||||
TARGET editor POST_BUILD
|
||||
|
|
|
@ -1 +1,17 @@
|
|||
#pragma once
|
||||
#pragma once
|
||||
|
||||
inline bool isDarkMode() {
|
||||
// https://stackoverflow.com/a/78854851/16255372
|
||||
#if defined(_WIN32)
|
||||
// Never read dark theme on Windows as the app currently renders with white color palette regardless
|
||||
return false;
|
||||
#elif QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
const auto scheme = QGuiApplication::styleHints()->colorScheme();
|
||||
return scheme == Qt::ColorScheme::Dark;
|
||||
#else
|
||||
const QPalette defaultPalette;
|
||||
const auto text = defaultPalette.color(QPalette::WindowText);
|
||||
const auto window = defaultPalette.color(QPalette::Window);
|
||||
return text.lightness() > window.lightness();
|
||||
#endif // QT_VERSION
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent), contextMenu(
|
|||
|
||||
void MainGLWidget::initializeGL() {
|
||||
glewInit();
|
||||
renderInit(NULL, width(), height());
|
||||
renderInit(width(), height());
|
||||
}
|
||||
|
||||
inline void playSound(QString path) {
|
||||
|
@ -63,7 +63,7 @@ extern std::weak_ptr<Part> draggingObject;
|
|||
extern std::optional<HandleFace> draggingHandle;
|
||||
extern Shader* shader;
|
||||
void MainGLWidget::paintGL() {
|
||||
::render(NULL);
|
||||
::render();
|
||||
}
|
||||
|
||||
bool isMouseRightDragging = false;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "mainwindow.h"
|
||||
#include "./ui_mainwindow.h"
|
||||
#include "script/commandedit.h"
|
||||
#include "common.h"
|
||||
#include "aboutdialog.h"
|
||||
#include "logger.h"
|
||||
|
@ -14,6 +15,7 @@
|
|||
#include <qevent.h>
|
||||
#include <qglobal.h>
|
||||
#include <qkeysequence.h>
|
||||
#include <qlabel.h>
|
||||
#include <qmessagebox.h>
|
||||
#include <qmimedata.h>
|
||||
#include <qnamespace.h>
|
||||
|
@ -74,10 +76,16 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
ui->actionRedo->setShortcuts({QKeySequence("Ctrl+Shift+Z"), QKeySequence("Ctrl+Y")});
|
||||
|
||||
QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() + QStringList { "./assets/icons" });
|
||||
|
||||
// Force theme under windows
|
||||
#ifdef _WIN32
|
||||
QIcon::setThemeName("editor");
|
||||
#else
|
||||
if (isDarkMode())
|
||||
QIcon::setFallbackThemeName("editor-dark");
|
||||
else
|
||||
QIcon::setThemeName("editor");
|
||||
QIcon::setFallbackThemeName("editor");
|
||||
#endif
|
||||
|
||||
// qApp->setStyle(QStyleFactory::create("fusion"));
|
||||
defaultMessageHandler = qInstallMessageHandler(logQtMessage);
|
||||
|
@ -111,6 +119,8 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
undoManager.SetUndoStateListener([&]() {
|
||||
updateToolbars();
|
||||
});
|
||||
|
||||
setUpCommandBar();
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent* evt) {
|
||||
|
@ -136,6 +146,14 @@ void MainWindow::closeEvent(QCloseEvent* evt) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::setUpCommandBar() {
|
||||
CommandEdit* commandEdit;
|
||||
QToolBar* commandBar = ui->commandBar;
|
||||
commandBar->layout()->setSpacing(5);
|
||||
commandBar->addWidget(new QLabel(tr("Command ")));
|
||||
commandBar->addWidget(commandEdit = new CommandEdit());
|
||||
}
|
||||
|
||||
void MainWindow::connectActionHandlers() {
|
||||
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_MOVE : TOOL_SELECT; updateToolbars(); });
|
||||
|
@ -592,19 +610,23 @@ ScriptDocument* MainWindow::findScriptWindow(std::shared_ptr<Script> script) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void MainWindow::openScriptDocument(std::shared_ptr<Script> script) {
|
||||
void MainWindow::openScriptDocument(std::shared_ptr<Script> script, int line) {
|
||||
// Document already exists, don't open it
|
||||
ScriptDocument* doc = findScriptWindow(script);
|
||||
if (doc != nullptr) {
|
||||
ui->mdiArea->setActiveSubWindow(doc);
|
||||
doc->setFocus();
|
||||
if (line > -1) doc->moveCursor(line);
|
||||
return;
|
||||
}
|
||||
|
||||
doc = new ScriptDocument(script);
|
||||
doc->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
if (line > -1) doc->moveCursor(line);
|
||||
ui->mdiArea->addSubWindow(doc);
|
||||
ui->mdiArea->setActiveSubWindow(doc);
|
||||
doc->showMaximized();
|
||||
doc->setFocus();
|
||||
}
|
||||
|
||||
void MainWindow::closeScriptDocument(std::shared_ptr<Script> script) {
|
||||
|
|
|
@ -58,7 +58,7 @@ public:
|
|||
GridSnappingMode snappingMode;
|
||||
bool editSoundEffects = true;
|
||||
|
||||
void openScriptDocument(std::shared_ptr<Script>);
|
||||
void openScriptDocument(std::shared_ptr<Script>, int line = -1);
|
||||
void closeScriptDocument(std::shared_ptr<Script>);
|
||||
|
||||
void openFile(std::string path);
|
||||
|
@ -69,11 +69,11 @@ public:
|
|||
private:
|
||||
PlaceDocument* placeDocument;
|
||||
|
||||
void setUpCommandBar();
|
||||
void connectActionHandlers();
|
||||
void updateToolbars();
|
||||
void closeEvent(QCloseEvent* evt) override;
|
||||
ScriptDocument* findScriptWindow(std::shared_ptr<Script>);
|
||||
|
||||
void connectActionHandlers();
|
||||
|
||||
std::optional<std::string> openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title = "");
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
<string>Openblocks Editor</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -172,7 +172,7 @@
|
|||
</widget>
|
||||
<widget class="QToolBar" name="editTools">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar_3</string>
|
||||
<string>Edit Tools</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
|
@ -237,7 +237,7 @@
|
|||
</widget>
|
||||
<widget class="QToolBar" name="toolBar">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar</string>
|
||||
<string>Sound Controls</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
|
@ -247,6 +247,17 @@
|
|||
</attribute>
|
||||
<addaction name="actionToggleEditSounds"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="commandBar">
|
||||
<property name="windowTitle">
|
||||
<string>Command Bar</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>BottomToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
<action name="actionAddPart">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
|
@ -306,8 +317,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-select">
|
||||
<normaloff>assets/icons/editor/drag.png</normaloff>assets/icons/editor/drag.png</iconset>
|
||||
<iconset theme="edit-select"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select Objects</string>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "outputtextview.h"
|
||||
#include "logger.h"
|
||||
#include "mainwindow.h"
|
||||
#include "objects/script.h"
|
||||
#include "panes/outputtextview.h"
|
||||
#include <QEvent>
|
||||
#include <QTextEdit>
|
||||
|
@ -12,8 +12,7 @@
|
|||
#include <string>
|
||||
|
||||
OutputTextView::OutputTextView(QWidget* parent) : QTextEdit(parent) {
|
||||
Logger::addLogListener(std::bind(&OutputTextView::handleLog, this, std::placeholders::_1, std::placeholders::_2));
|
||||
Logger::addLogListener(std::bind(&OutputTextView::handleLogTrace, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
||||
Logger::addLogListener(std::bind(&OutputTextView::handleLog, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
ensureCursorVisible();
|
||||
|
||||
QFont font("");
|
||||
|
@ -36,13 +35,9 @@ OutputTextView::OutputTextView(QWidget* parent) : QTextEdit(parent) {
|
|||
|
||||
OutputTextView::~OutputTextView() = default;
|
||||
|
||||
void OutputTextView::handleLog(Logger::LogLevel logLevel, std::string message) {
|
||||
void OutputTextView::handleLog(Logger::LogLevel logLevel, std::string message, Logger::ScriptSource source) {
|
||||
if (logLevel == Logger::LogLevel::DEBUG) return;
|
||||
|
||||
// Skip if trace, as that is handled by handleLogTrace
|
||||
if (logLevel == Logger::LogLevel::TRACE && !message.starts_with("Stack"))
|
||||
return;
|
||||
|
||||
// https://stackoverflow.com/a/61722734/16255372
|
||||
moveCursor(QTextCursor::MoveOperation::End);
|
||||
QTextCursor cursor = textCursor();
|
||||
|
@ -58,24 +53,13 @@ void OutputTextView::handleLog(Logger::LogLevel logLevel, std::string message) {
|
|||
format.setFontWeight(QFont::Bold);
|
||||
}
|
||||
|
||||
cursor.insertText(message.c_str(), format);
|
||||
cursor.insertText("\n", QTextCharFormat());
|
||||
}
|
||||
|
||||
void OutputTextView::handleLogTrace(std::string message, std::string source, int line, void* userData) {
|
||||
std::weak_ptr<Script>* script = (std::weak_ptr<Script>*)userData;
|
||||
|
||||
// https://stackoverflow.com/a/61722734/16255372
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextCharFormat format = cursor.charFormat();
|
||||
format.setForeground(QColor(0, 127, 255));
|
||||
|
||||
if (userData != nullptr && !script->expired()) {
|
||||
// Add anchor point if source is provided
|
||||
if (source.script != nullptr) {
|
||||
int id = stackTraceScriptsLastId++;
|
||||
stackTraceScripts[id] = *script;
|
||||
stackTraceScripts[id] = source.script;
|
||||
|
||||
format.setAnchor(true);
|
||||
format.setAnchorHref(QString::number(id));
|
||||
format.setAnchorHref(QString::number(id) + ":" + QString::number(source.line));
|
||||
}
|
||||
|
||||
cursor.insertText(message.c_str(), format);
|
||||
|
@ -87,11 +71,13 @@ void OutputTextView::mousePressEvent(QMouseEvent *e) {
|
|||
QString anchor = anchorAt(e->pos());
|
||||
if (anchor == "" || e->modifiers() & Qt::AltModifier) return QTextEdit::mousePressEvent(e);
|
||||
|
||||
auto script = stackTraceScripts[anchor.toInt()];
|
||||
int idx = anchor.indexOf(":");
|
||||
int id = anchor.mid(0, idx).toInt(), line = anchor.mid(idx+1).toInt();
|
||||
auto script = stackTraceScripts[id];
|
||||
if (script.expired()) return QTextEdit::mousePressEvent(e);
|
||||
|
||||
MainWindow* mainWnd = dynamic_cast<MainWindow*>(window());
|
||||
mainWnd->openScriptDocument(script.lock());
|
||||
mainWnd->openScriptDocument(script.lock(), line);
|
||||
}
|
||||
|
||||
void OutputTextView::mouseReleaseEvent(QMouseEvent *e) {
|
||||
|
|
|
@ -14,8 +14,7 @@ private:
|
|||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
|
||||
void handleLog(Logger::LogLevel, std::string);
|
||||
void handleLogTrace(std::string, std::string, int, void*);
|
||||
void handleLog(Logger::LogLevel, std::string, Logger::ScriptSource source);
|
||||
|
||||
std::map<int, std::weak_ptr<Script>> stackTraceScripts;
|
||||
int stackTraceScriptsLastId = 0;
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include <QStyledItemDelegate>
|
||||
#include <QPainter>
|
||||
#include <QTime>
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <qapplication.h>
|
||||
|
@ -24,6 +23,14 @@
|
|||
#include <qnamespace.h>
|
||||
#include <qtreewidget.h>
|
||||
|
||||
QDoubleSpinBox* makeDoubleSpinBox(QWidget* parent = nullptr) {
|
||||
QDoubleSpinBox* spinBox = new QDoubleSpinBox(parent);
|
||||
spinBox->setMaximum(INFINITY);
|
||||
spinBox->setMinimum(-INFINITY);
|
||||
spinBox->setDecimals(4);
|
||||
return spinBox;
|
||||
}
|
||||
|
||||
class PropertiesItemDelegate : public QStyledItemDelegate {
|
||||
PropertiesView* view;
|
||||
public:
|
||||
|
@ -64,7 +71,7 @@ public:
|
|||
Vector3 vector = currentValue.get<Vector3>();
|
||||
float value = componentName == "X" ? vector.X() : componentName == "Y" ? vector.Y() : componentName == "Z" ? vector.Z() : 0;
|
||||
|
||||
QDoubleSpinBox* spinBox = new QDoubleSpinBox(parent);
|
||||
QDoubleSpinBox* spinBox = makeDoubleSpinBox(parent);
|
||||
spinBox->setValue(value);
|
||||
|
||||
return spinBox;
|
||||
|
@ -75,9 +82,9 @@ public:
|
|||
|
||||
if (meta.type.descriptor == &FLOAT_TYPE) {
|
||||
QDoubleSpinBox* spinBox = new QDoubleSpinBox(parent);
|
||||
spinBox->setValue(currentValue.get<float>());
|
||||
spinBox->setMinimum(-INFINITY);
|
||||
spinBox->setMaximum(INFINITY);
|
||||
spinBox->setValue(currentValue.get<float>());
|
||||
|
||||
if (meta.flags & PROP_UNIT_FLOAT) {
|
||||
spinBox->setMinimum(0);
|
||||
|
@ -221,7 +228,8 @@ public:
|
|||
: componentName == "Z" ? Vector3(prev.X(), prev.Y(), value) : prev;
|
||||
|
||||
inst->SetProperty(propertyName, newVector).expect();
|
||||
view->rebuildCompositeProperty(view->itemFromIndex(index.parent()), &Vector3::TYPE, newVector);
|
||||
// SetProperty above already causes the composite to be rebuilt. So we get rid of it here to prevent errors
|
||||
// view->rebuildCompositeProperty(view->itemFromIndex(index.parent()), &Vector3::TYPE, newVector);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
115
editor/script/commandedit.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#include "commandedit.h"
|
||||
#include "common.h"
|
||||
#include "logger.h"
|
||||
#include "lua.h"
|
||||
#include "objects/service/script/scriptcontext.h"
|
||||
#include "luaapis.h" // IWYU pragma: keep
|
||||
#include <qevent.h>
|
||||
#include <qlineedit.h>
|
||||
#include <qnamespace.h>
|
||||
|
||||
int script_errhandler(lua_State*);
|
||||
|
||||
CommandEdit::CommandEdit(QWidget* parent) : QLineEdit(parent) {
|
||||
connect(this, &QLineEdit::returnPressed, this, &CommandEdit::executeCommand);
|
||||
}
|
||||
|
||||
CommandEdit::~CommandEdit() = default;
|
||||
|
||||
void CommandEdit::executeCommand() {
|
||||
std::string command = this->text().toStdString();
|
||||
|
||||
// Output
|
||||
Logger::infof("> %s", command.c_str());
|
||||
|
||||
// Select all so that the user can type over it
|
||||
this->selectAll();
|
||||
|
||||
// Execute via Lua
|
||||
auto context = gDataModel->GetService<ScriptContext>();
|
||||
lua_State* L = context->state;
|
||||
|
||||
int top = lua_gettop(L);
|
||||
lua_State* Lt = lua_newthread(L);
|
||||
|
||||
// Push wrapper as thread function
|
||||
lua_getfield(Lt, LUA_REGISTRYINDEX, "LuaPCallWrapper");
|
||||
|
||||
// Load source code and push onto thread as upvalue for wrapper
|
||||
int status = luaL_loadstring(Lt, command.c_str());
|
||||
if (status != LUA_OK) {
|
||||
// Failed to parse/load chunk
|
||||
Logger::error(lua_tostring(Lt, -1));
|
||||
|
||||
lua_settop(L, top);
|
||||
return;
|
||||
}
|
||||
|
||||
getOrCreateEnvironment(Lt);
|
||||
lua_setfenv(Lt, -2); // Set env of loaded function
|
||||
|
||||
// Push our error handler and then generate the wrapped function
|
||||
lua_pushcfunction(Lt, script_errhandler);
|
||||
lua_call(Lt, 2, 1);
|
||||
|
||||
|
||||
// Resume the thread
|
||||
lua_resume(Lt, 0);
|
||||
|
||||
lua_pop(L, 1); // Pop the thread
|
||||
lua_settop(L, top);
|
||||
|
||||
// Push to history
|
||||
if (commandHistory.size() == 0 || commandHistory.back() != command) {
|
||||
historyIndex = commandHistory.size();
|
||||
commandHistory.push_back(command);
|
||||
}
|
||||
};
|
||||
|
||||
// Gets the command bar environment from the registry, or creates a new one and registers it
|
||||
void CommandEdit::getOrCreateEnvironment(lua_State* L) {
|
||||
auto context = gDataModel->GetService<ScriptContext>();
|
||||
|
||||
// Try to find existing environment
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, "commandBarEnv");
|
||||
if (!lua_isnil(L, -1))
|
||||
return; // Return the found environment
|
||||
lua_pop(L, 1); // Pop nil
|
||||
|
||||
// Initialize script globals
|
||||
context->NewEnvironment(L); // Pushes envtable, metatable
|
||||
|
||||
// Set source in metatable
|
||||
lua_pushstring(L, "commandbar");
|
||||
lua_setfield(L, -2, "source");
|
||||
|
||||
lua_pop(L, 1); // Pop metatable
|
||||
|
||||
// Register it
|
||||
lua_pushvalue(L, -1); // Copy
|
||||
lua_setfield(L, LUA_REGISTRYINDEX, "commandBarEnv");
|
||||
|
||||
// Remainder on stack:
|
||||
// 1. Env table
|
||||
}
|
||||
|
||||
void CommandEdit::keyPressEvent(QKeyEvent* evt) {
|
||||
switch (evt->key()) {
|
||||
case Qt::Key_Up:
|
||||
if (historyIndex > 0)
|
||||
historyIndex--;
|
||||
|
||||
if (commandHistory.size() > 0)
|
||||
this->setText(QString::fromStdString(commandHistory[historyIndex]));
|
||||
return;
|
||||
case Qt::Key_Down:
|
||||
if (historyIndex+1 < (int)commandHistory.size())
|
||||
historyIndex++;
|
||||
|
||||
if (commandHistory.size() > 0)
|
||||
this->setText(QString::fromStdString(commandHistory[historyIndex]));
|
||||
return;
|
||||
}
|
||||
|
||||
return QLineEdit::keyPressEvent(evt);
|
||||
}
|
21
editor/script/commandedit.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <qlineedit.h>
|
||||
#include <vector>
|
||||
|
||||
struct lua_State;
|
||||
|
||||
class CommandEdit : public QLineEdit {
|
||||
Q_OBJECT
|
||||
|
||||
std::vector<std::string> commandHistory;
|
||||
int historyIndex = 0;
|
||||
|
||||
void executeCommand();
|
||||
void getOrCreateEnvironment(lua_State* L);
|
||||
public:
|
||||
CommandEdit(QWidget* parent = nullptr);
|
||||
~CommandEdit();
|
||||
|
||||
void keyPressEvent(QKeyEvent *) override;
|
||||
};
|
|
@ -14,27 +14,15 @@
|
|||
#include <qglobal.h>
|
||||
#include <qlayout.h>
|
||||
#include <qtextformat.h>
|
||||
#include "mainwindow.h"
|
||||
#include "objects/script.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include <QPalette>
|
||||
#include <QStyleHints>
|
||||
#include "mainwindow.h"
|
||||
#include "editorcommon.h"
|
||||
#include "objects/script.h"
|
||||
#include "datatypes/variant.h"
|
||||
|
||||
QsciAPIs* makeApis(QsciLexer*);
|
||||
|
||||
inline bool isDarkMode() {
|
||||
// https://stackoverflow.com/a/78854851/16255372
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
const auto scheme = QGuiApplication::styleHints()->colorScheme();
|
||||
return scheme == Qt::ColorScheme::Dark;
|
||||
#else
|
||||
const QPalette defaultPalette;
|
||||
const auto text = defaultPalette.color(QPalette::WindowText);
|
||||
const auto window = defaultPalette.color(QPalette::Window);
|
||||
return text.lightness() > window.lightness();
|
||||
#endif // QT_VERSION
|
||||
}
|
||||
|
||||
std::map<int, const QColor> DARK_MODE_COLOR_SCHEME = {{
|
||||
{QsciLexerLua::Comment, QColor("#808080")},
|
||||
{QsciLexerLua::LineComment, QColor("#808080")},
|
||||
|
@ -71,7 +59,7 @@ class ObLuaLexer : public QsciLexerLua {
|
|||
//"foreach foreachi getn "
|
||||
|
||||
// Openblocks extensions
|
||||
"shared require game workspace "
|
||||
"shared require game workspace Instance Instance.new "
|
||||
|
||||
"_G _VERSION getfenv getmetatable ipairs loadstring "
|
||||
"next pairs pcall rawequal rawget rawset select "
|
||||
|
@ -151,7 +139,8 @@ ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
|
|||
scintilla->setCaretForegroundColor(palette().text().color());
|
||||
scintilla->setFont(font);
|
||||
scintilla->setTabWidth(4);
|
||||
|
||||
scintilla->setEolMode(QsciScintilla::EolUnix); // LF endings
|
||||
|
||||
scintilla->setText(QString::fromStdString(script->source));
|
||||
|
||||
ObLuaLexer* lexer = new ObLuaLexer;
|
||||
|
@ -190,6 +179,13 @@ ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
|
|||
ScriptDocument::~ScriptDocument() {
|
||||
}
|
||||
|
||||
void ScriptDocument::moveCursor(int line) {
|
||||
if (line == -1) return;
|
||||
|
||||
int lineLength = scintilla->lineLength(line-1);
|
||||
scintilla->setCursorPosition(line-1, lineLength-1);
|
||||
}
|
||||
|
||||
QsciAPIs* makeApis(QsciLexer* lexer) {
|
||||
QsciAPIs* apis = new QsciAPIs(lexer);
|
||||
|
||||
|
|
|
@ -17,4 +17,5 @@ public:
|
|||
~ScriptDocument() override;
|
||||
|
||||
inline std::shared_ptr<Script> getScript() { return script; }
|
||||
void moveCursor(int line);
|
||||
};
|
|
@ -8,7 +8,8 @@
|
|||
"stb",
|
||||
"reactphysics3d",
|
||||
"pkgconf",
|
||||
"luajit"
|
||||
"luajit",
|
||||
"freetype"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
|
|