Compare commits
9 commits
de0acda8ac
...
e0ac5398f3
Author | SHA1 | Date | |
---|---|---|---|
e0ac5398f3 | |||
a74b409459 | |||
b9f68ee160 | |||
b06098e4c7 | |||
18b573db97 | |||
882a215b36 | |||
5e605a9692 | |||
a022e682b7 | |||
6f9856c384 |
BIN
assets/icons/editor/actions/48/application-exit.png
Normal file
After Width: | Height: | Size: 508 B |
BIN
assets/icons/editor/actions/48/audio-volume-high.png
Normal file
After Width: | Height: | Size: 610 B |
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
Before Width: | Height: | Size: 688 B After Width: | Height: | Size: 688 B |
Before Width: | Height: | Size: 774 B After Width: | Height: | Size: 774 B |
Before Width: | Height: | Size: 774 B After Width: | Height: | Size: 774 B |
Before Width: | Height: | Size: 354 B |
BIN
assets/icons/editor/actions/48/edit-copy.png
Normal file
After Width: | Height: | Size: 309 B |
BIN
assets/icons/editor/actions/48/edit-cut.png
Normal file
After Width: | Height: | Size: 648 B |
BIN
assets/icons/editor/actions/48/edit-delete.png
Normal file
After Width: | Height: | Size: 537 B |
BIN
assets/icons/editor/actions/48/edit-paste.png
Normal file
After Width: | Height: | Size: 605 B |
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 354 B |
BIN
assets/icons/editor/actions/48/media-playback-pause.png
Normal file
After Width: | Height: | Size: 721 B |
BIN
assets/icons/editor/actions/48/media-playback-start.png
Normal file
After Width: | Height: | Size: 717 B |
BIN
assets/icons/editor/actions/48/media-playback-stop.png
Normal file
After Width: | Height: | Size: 695 B |
Before Width: | Height: | Size: 294 B |
Before Width: | Height: | Size: 688 B |
BIN
assets/icons/editor/actions/48/surface-hinge.png
Normal file
After Width: | Height: | Size: 383 B |
BIN
assets/icons/editor/actions/48/surface-motor.png
Normal file
After Width: | Height: | Size: 395 B |
|
@ -9,10 +9,10 @@ uniform mat4 model;
|
|||
uniform mat4 view;
|
||||
uniform mat4 projection;
|
||||
uniform vec3 scale;
|
||||
uniform float thickness;
|
||||
|
||||
void main()
|
||||
{
|
||||
float thickness = 0.4;
|
||||
vec3 distFromEdge = sign(aPos) * 0.5 - aPos;
|
||||
vec3 tVec = (scale * sign(aPos) * 0.5 - distFromEdge * thickness) / scale;
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ static std::string castFromVariant(std::string valueStr, std::string fieldType)
|
|||
}
|
||||
|
||||
std::string mappedType = MAPPED_TYPE[fieldType];
|
||||
return valueStr + ".get<" + (!mappedType.empty() ? mappedType : fieldType) + ">()";
|
||||
return "(" + fieldType + ")" + valueStr + ".get<" + (!mappedType.empty() ? mappedType : fieldType) + ">()";
|
||||
}
|
||||
|
||||
static std::string castToVariant(std::string valueStr, std::string fieldType) {
|
||||
|
|
|
@ -11,7 +11,7 @@ std::shared_ptr<DataModel> gDataModel = DataModel::New();
|
|||
std::shared_ptr<DataModel> editModeDataModel = gDataModel;
|
||||
std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
||||
std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
||||
std::shared_ptr<Handles> editorToolHandles = Handles::New();
|
||||
Handles editorToolHandles;
|
||||
|
||||
|
||||
std::vector<InstanceRefWeak> currentSelection;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/handles.h"
|
||||
#include "handles.h"
|
||||
#include "objects/workspace.h"
|
||||
#include "objects/datamodel.h"
|
||||
#include "camera.h"
|
||||
|
@ -22,7 +22,7 @@ extern std::shared_ptr<DataModel> editModeDataModel;
|
|||
inline std::shared_ptr<Workspace> gWorkspace() { return std::dynamic_pointer_cast<Workspace>(gDataModel->services["Workspace"]); }
|
||||
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
||||
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
||||
extern std::shared_ptr<Handles> editorToolHandles;
|
||||
extern Handles editorToolHandles;
|
||||
|
||||
void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer = false);
|
||||
const std::vector<InstanceRefWeak> getSelection();
|
||||
|
|
|
@ -64,7 +64,11 @@ const Data::String Data::CFrame::ToString() const {
|
|||
}
|
||||
|
||||
Data::CFrame Data::CFrame::pointToward(Vector3 position, Vector3 toward) {
|
||||
return Data::CFrame(position, position + toward, (abs(glm::dot((glm::vec3)toward, glm::vec3(0, 1, 0))) > 0.999) ? glm::vec3(0, 0, 1) : glm::vec3(0, 1, 0));
|
||||
return Data::CFrame(position, position + toward, (abs(toward.Dot(Vector3(0, 1, 0))) > 0.999) ? Vector3(0, 0, 1) : Vector3(0, 1, 0));
|
||||
}
|
||||
|
||||
Data::CFrame Data::CFrame::pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right) {
|
||||
return Data::CFrame(position, position + toward, (abs(toward.Dot(up)) > 0.999) ? right : up);
|
||||
}
|
||||
|
||||
Data::CFrame::operator glm::mat4() const {
|
||||
|
|
|
@ -30,6 +30,11 @@ namespace Data {
|
|||
|
||||
// Same as CFrame(position, position + toward), but makes sure that up and toward are not linearly dependant
|
||||
static CFrame pointToward(Vector3 position, Vector3 toward);
|
||||
// Creates a cframe looking at position + toward, whilst aligning its up to up.
|
||||
// If up and toward are approximately linearly dependent (their absolute dot product > 0.999),
|
||||
// then the right is used instead
|
||||
// Up and right must NOT be linearly dependent
|
||||
static CFrame pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right);
|
||||
|
||||
DEF_DATA_PROP static const CFrame IDENTITY;
|
||||
static const CFrame YToZ;
|
||||
|
|
136
core/src/handles.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include "handles.h"
|
||||
#include "common.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "math_helper.h"
|
||||
#include <glm/ext/scalar_common.hpp>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <reactphysics3d/collision/RaycastInfo.h>
|
||||
#include <reactphysics3d/engine/PhysicsCommon.h>
|
||||
#include <reactphysics3d/engine/PhysicsWorld.h>
|
||||
#include <reactphysics3d/mathematics/Transform.h>
|
||||
|
||||
HandleFace HandleFace::XPos(0, glm::vec3(1,0,0));
|
||||
HandleFace HandleFace::XNeg(1, glm::vec3(-1,0,0));
|
||||
HandleFace HandleFace::YPos(2, glm::vec3(0,1,0));
|
||||
HandleFace HandleFace::YNeg(3, glm::vec3(0,-1,0));
|
||||
HandleFace HandleFace::ZPos(4, glm::vec3(0,0,1));
|
||||
HandleFace HandleFace::ZNeg(5, glm::vec3(0,0,-1));
|
||||
std::array<HandleFace, 6> HandleFace::Faces { HandleFace::XPos, HandleFace::XNeg, HandleFace::YPos, HandleFace::YNeg, HandleFace::ZPos, HandleFace::ZNeg };
|
||||
|
||||
static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
|
||||
|
||||
// Shitty solution
|
||||
static rp3d::PhysicsCommon common;
|
||||
static rp3d::PhysicsWorld* world = common.createPhysicsWorld();
|
||||
|
||||
std::shared_ptr<Part> getHandleAdornee() {
|
||||
for (std::weak_ptr<Instance> inst : getSelection()) {
|
||||
if (!inst.expired() && inst.lock()->IsA<Part>())
|
||||
return inst.lock()->CastTo<Part>().expect();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
CFrame partCFrameFromHandlePos(HandleFace face, Vector3 newPos) {
|
||||
auto adornee = getHandleAdornee();
|
||||
if (adornee == nullptr) return CFrame(glm::vec3(0,0,0), (Vector3)glm::vec3(0,0,0));
|
||||
|
||||
CFrame localFrame = editorToolHandles.worldMode ? CFrame::IDENTITY + adornee->position() : adornee->cframe;
|
||||
CFrame inverseFrame = localFrame.Inverse();
|
||||
Vector3 handleOffset = editorToolHandles.worldMode ? ((Vector3::ONE * 2.f) + adornee->GetAABB() * 0.5f) : Vector3(2.f + adornee->size * 0.5f);
|
||||
|
||||
Vector3 handlePos = localFrame * (handleOffset * face.normal);
|
||||
|
||||
// glm::vec3 localPos = inverseFrame * newPos;
|
||||
glm::vec3 newPartPos = newPos - localFrame.Rotation() * (handleOffset * face.normal);
|
||||
return adornee->cframe.Rotation() + newPartPos;
|
||||
}
|
||||
|
||||
std::optional<HandleFace> raycastHandle(rp3d::Ray ray) {
|
||||
for (HandleFace face : HandleFace::Faces) {
|
||||
CFrame cframe = getHandleCFrame(face);
|
||||
// Implement manual detection via boxes instead of... this shit
|
||||
// This code also hardly works, and is not good at all... Hooo nope.
|
||||
rp3d::RigidBody* body = world->createRigidBody(CFrame::IDENTITY + cframe.Position());
|
||||
body->addCollider(common.createBoxShape(cframe.Rotation() * Vector3(handleSize(face) / 2.f)), rp3d::Transform::identity());
|
||||
|
||||
rp3d::RaycastInfo info;
|
||||
if (body->raycast(ray, info)) {
|
||||
world->destroyRigidBody(body);
|
||||
return face;
|
||||
}
|
||||
|
||||
world->destroyRigidBody(body);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Vector3 handleSize(HandleFace face) {
|
||||
if (editorToolHandles.handlesType == HandlesType::MoveHandles)
|
||||
return glm::vec3(0.5f, 0.5f, 2.f);
|
||||
return glm::vec3(1,1,1);
|
||||
}
|
||||
|
||||
static int getAABBOfSelection(glm::vec3& pos, glm::vec3& size, glm::vec3& min, glm::vec3& max) {
|
||||
int count = 0;
|
||||
for (std::weak_ptr<Instance> inst : getSelection()) {
|
||||
if (inst.expired() || !inst.lock()->IsA<Part>()) continue;
|
||||
std::shared_ptr<Part> part = inst.lock()->CastTo<Part>().expect();
|
||||
|
||||
if (count == 0)
|
||||
min = part->position(), max = part->position();
|
||||
count++;
|
||||
|
||||
Vector3 aabbSize = part->GetAABB();
|
||||
expandAABB(min, max, part->position() - aabbSize / 2.f);
|
||||
expandAABB(min, max, part->position() + aabbSize / 2.f);
|
||||
}
|
||||
|
||||
getAABBCoords(pos, size, min, max);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static std::shared_ptr<Part> getFirstSelectedPart() {
|
||||
for (std::weak_ptr<Instance> inst : getSelection()) {
|
||||
if (inst.expired() || !inst.lock()->IsA<Part>()) continue;
|
||||
|
||||
return inst.lock()->CastTo<Part>().expect();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
CFrame getLocalHandleCFrame(HandleFace face) {
|
||||
glm::vec3 _boxPos, boxSize, _boxMin, _boxMax;
|
||||
int count = getAABBOfSelection(_boxPos, boxSize, _boxMin, _boxMax);
|
||||
|
||||
Vector3 size;
|
||||
if (count == 1 && !editorToolHandles.worldMode)
|
||||
size = getFirstSelectedPart()->size;
|
||||
else
|
||||
size = boxSize;
|
||||
|
||||
// Since rotation displays rings, all handles must be the same distance from origin in order for the
|
||||
// rings to be circular
|
||||
if (editorToolHandles.handlesType == HandlesType::RotateHandles)
|
||||
size = Vector3::ONE * fmax(fmax(size.X(), size.Y()), size.Z());
|
||||
|
||||
CFrame cframe = CFrame::pointToward(face.normal * ((glm::vec3)size * 0.5f + 2.f), -face.normal);
|
||||
return cframe;
|
||||
}
|
||||
|
||||
CFrame getHandleCFrame(HandleFace face) {
|
||||
glm::vec3 boxPos, boxSize, _boxMin, _boxMax;
|
||||
int count = getAABBOfSelection(boxPos, boxSize, _boxMin, _boxMax);
|
||||
|
||||
if (count == 1 && !editorToolHandles.worldMode) {
|
||||
auto part = getFirstSelectedPart();
|
||||
return part->cframe * getLocalHandleCFrame(face);
|
||||
} else
|
||||
return getLocalHandleCFrame(face) + boxPos;
|
||||
}
|
46
core/src/handles.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include "datatypes/cframe.h"
|
||||
#include "objects/part.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
class HandleFace {
|
||||
HandleFace(int index, glm::vec3 normal) : index(index), normal(normal){}
|
||||
|
||||
public:
|
||||
int index;
|
||||
glm::vec3 normal;
|
||||
|
||||
static HandleFace XPos;
|
||||
static HandleFace XNeg;
|
||||
static HandleFace YPos;
|
||||
static HandleFace YNeg;
|
||||
static HandleFace ZPos;
|
||||
static HandleFace ZNeg;
|
||||
static std::array<HandleFace, 6> Faces;
|
||||
};
|
||||
|
||||
enum HandlesType {
|
||||
MoveHandles,
|
||||
ScaleHandles,
|
||||
RotateHandles,
|
||||
};
|
||||
|
||||
struct Handles {
|
||||
bool nixAxes = false; // XYZ -> ZXY, used with rotation
|
||||
bool active;
|
||||
HandlesType handlesType;
|
||||
|
||||
// World-space handles vs local-space handles
|
||||
bool worldMode = false;
|
||||
};
|
||||
|
||||
std::shared_ptr<Part> getHandleAdornee();
|
||||
CFrame getHandleCFrame(HandleFace face);
|
||||
CFrame partCFrameFromHandlePos(HandleFace face, Vector3 newPos);
|
||||
Vector3 handleSize(HandleFace face);
|
||||
std::optional<HandleFace> raycastHandle(rp3d::Ray ray);
|
||||
|
||||
// Gets the cframe of the handle local to the center of the selected objects
|
||||
CFrame getLocalHandleCFrame(HandleFace face);
|
|
@ -100,4 +100,26 @@ void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3
|
|||
|
||||
r_ps = (1 - s) * p_p0 + s * p_p1;
|
||||
r_qt = (1 - t) * p_q0 + t * p_q1;
|
||||
}
|
||||
|
||||
void expandAABB(glm::vec3& min, glm::vec3& max, glm::vec3 point) {
|
||||
min = glm::vec3(glm::min(min.x, point.x), glm::min(min.y, point.y), glm::min(min.z, point.z));
|
||||
max = glm::vec3(glm::max(max.x, point.x), glm::max(max.y, point.y), glm::max(max.z, point.z));
|
||||
}
|
||||
|
||||
void computeAABBFromPoints(glm::vec3& min, glm::vec3& max, glm::vec3* points, int count) {
|
||||
if (count == 0) return;
|
||||
|
||||
min = points[0];
|
||||
max = points[0];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
min = glm::vec3(glm::min(min.x, points[i].x), glm::min(min.y, points[i].y), glm::min(min.z, points[i].z));
|
||||
max = glm::vec3(glm::max(max.x, points[i].x), glm::max(max.y, points[i].y), glm::max(max.z, points[i].z));
|
||||
}
|
||||
}
|
||||
|
||||
void getAABBCoords(glm::vec3 &pos, glm::vec3 &size, glm::vec3 min, glm::vec3 max) {
|
||||
pos = (max + min) / 2.f;
|
||||
size = (max - min);
|
||||
}
|
|
@ -2,4 +2,8 @@
|
|||
#include <glm/glm.hpp>
|
||||
|
||||
// From godot/editor/plugins/gizmos/gizmo_3d_helper.h
|
||||
void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3 &p_p1, const glm::vec3 &p_q0, const glm::vec3 &p_q1, glm::vec3 &r_ps, glm::vec3 &r_qt);
|
||||
void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3 &p_p1, const glm::vec3 &p_q0, const glm::vec3 &p_q1, glm::vec3 &r_ps, glm::vec3 &r_qt);
|
||||
|
||||
void expandAABB(glm::vec3& min, glm::vec3& max, glm::vec3 point);
|
||||
void computeAABBFromPoints(glm::vec3& min, glm::vec3& max, glm::vec3* points, int count);
|
||||
void getAABBCoords(glm::vec3& pos, glm::vec3& size, glm::vec3 min, glm::vec3 max);
|
|
@ -9,7 +9,7 @@ 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) {
|
||||
if (!newParent || newParent.value()->GetClass() != &DataModel::TYPE) {
|
||||
Logger::fatalErrorf("Service %s was parented to object of type %s", GetClass()->className, newParent ? newParent.value()->GetClass()->className : "NULL");
|
||||
Logger::fatalErrorf("Service %s was parented to object of type %s", GetClass()->className.c_str(), newParent ? newParent.value()->GetClass()->className.c_str() : "NULL");
|
||||
panic();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
#include "handles.h"
|
||||
#include "common.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include <glm/ext/scalar_common.hpp>
|
||||
#include <optional>
|
||||
#include <reactphysics3d/collision/RaycastInfo.h>
|
||||
#include <reactphysics3d/engine/PhysicsCommon.h>
|
||||
#include <reactphysics3d/engine/PhysicsWorld.h>
|
||||
#include <reactphysics3d/mathematics/Transform.h>
|
||||
|
||||
HandleFace HandleFace::XPos(0, glm::vec3(1,0,0));
|
||||
HandleFace HandleFace::XNeg(1, glm::vec3(-1,0,0));
|
||||
HandleFace HandleFace::YPos(2, glm::vec3(0,1,0));
|
||||
HandleFace HandleFace::YNeg(3, glm::vec3(0,-1,0));
|
||||
HandleFace HandleFace::ZPos(4, glm::vec3(0,0,1));
|
||||
HandleFace HandleFace::ZNeg(5, glm::vec3(0,0,-1));
|
||||
std::array<HandleFace, 6> HandleFace::Faces { HandleFace::XPos, HandleFace::XNeg, HandleFace::YPos, HandleFace::YNeg, HandleFace::ZPos, HandleFace::ZNeg };
|
||||
|
||||
static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
|
||||
|
||||
// Shitty solution
|
||||
static rp3d::PhysicsCommon common;
|
||||
static rp3d::PhysicsWorld* world = common.createPhysicsWorld();
|
||||
|
||||
Handles::Handles(): Instance(&TYPE) {
|
||||
}
|
||||
|
||||
CFrame Handles::GetCFrameOfHandle(HandleFace face) {
|
||||
if (adornee.expired()) return CFrame(glm::vec3(0,0,0), (Vector3)glm::vec3(0,0,0));
|
||||
|
||||
CFrame localFrame = worldMode ? CFrame::IDENTITY + adornee.lock()->position() : adornee.lock()->cframe;
|
||||
|
||||
Vector3 handleNormal = face.normal;
|
||||
if (nixAxes)
|
||||
handleNormal = XYZToZXY * face.normal;
|
||||
|
||||
// We don't want this to align with local * face.normal, or else we have problems.
|
||||
glm::vec3 upAxis(0, 0, 1);
|
||||
if (glm::abs(glm::dot(glm::vec3(localFrame.Rotation() * handleNormal), upAxis)) > 0.9999f)
|
||||
upAxis = glm::vec3(0, 1, 0);
|
||||
|
||||
glm::vec3 partSize = handlesType == HandlesType::RotateHandles ? glm::vec3(glm::max(adornee.lock()->size.x, adornee.lock()->size.y, adornee.lock()->size.z)) : adornee.lock()->size;
|
||||
Vector3 handleOffset = this->worldMode ? ((Vector3::ONE * 2.f) + adornee.lock()->GetAABB() * 0.5f) : Vector3(2.f + partSize * 0.5f);
|
||||
Vector3 handlePos = localFrame * (handleOffset * handleNormal);
|
||||
CFrame cframe(handlePos, handlePos + localFrame.Rotation() * -handleNormal, upAxis);
|
||||
|
||||
return cframe;
|
||||
}
|
||||
|
||||
CFrame Handles::PartCFrameFromHandlePos(HandleFace face, Vector3 newPos) {
|
||||
if (adornee.expired()) return CFrame(glm::vec3(0,0,0), (Vector3)glm::vec3(0,0,0));
|
||||
|
||||
CFrame localFrame = worldMode ? CFrame::IDENTITY + adornee.lock()->position() : adornee.lock()->cframe;
|
||||
CFrame inverseFrame = localFrame.Inverse();
|
||||
Vector3 handleOffset = this->worldMode ? ((Vector3::ONE * 2.f) + adornee.lock()->GetAABB() * 0.5f) : Vector3(2.f + adornee.lock()->size * 0.5f);
|
||||
|
||||
Vector3 handlePos = localFrame * (handleOffset * face.normal);
|
||||
|
||||
// glm::vec3 localPos = inverseFrame * newPos;
|
||||
glm::vec3 newPartPos = newPos - localFrame.Rotation() * (handleOffset * face.normal);
|
||||
return adornee.lock()->cframe.Rotation() + newPartPos;
|
||||
}
|
||||
|
||||
std::optional<HandleFace> Handles::RaycastHandle(rp3d::Ray ray) {
|
||||
for (HandleFace face : HandleFace::Faces) {
|
||||
CFrame cframe = GetCFrameOfHandle(face);
|
||||
// Implement manual detection via boxes instead of... this shit
|
||||
// This code also hardly works, and is not good at all... Hooo nope.
|
||||
rp3d::RigidBody* body = world->createRigidBody(CFrame::IDENTITY + cframe.Position());
|
||||
body->addCollider(common.createBoxShape(cframe.Rotation() * Vector3(HandleSize(face) / 2.f)), rp3d::Transform::identity());
|
||||
|
||||
rp3d::RaycastInfo info;
|
||||
if (body->raycast(ray, info)) {
|
||||
world->destroyRigidBody(body);
|
||||
return face;
|
||||
}
|
||||
|
||||
world->destroyRigidBody(body);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Vector3 Handles::HandleSize(HandleFace face) {
|
||||
if (handlesType == HandlesType::MoveHandles)
|
||||
return glm::vec3(0.5f, 0.5f, 2.f);
|
||||
return glm::vec3(1,1,1);
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "objects/annotation.h"
|
||||
#include "objects/base/service.h"
|
||||
#include "objects/part.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <reactphysics3d/body/RigidBody.h>
|
||||
|
||||
class HandleFace {
|
||||
HandleFace(int index, glm::vec3 normal) : index(index), normal(normal){}
|
||||
|
||||
public:
|
||||
int index;
|
||||
glm::vec3 normal;
|
||||
|
||||
static HandleFace XPos;
|
||||
static HandleFace XNeg;
|
||||
static HandleFace YPos;
|
||||
static HandleFace YNeg;
|
||||
static HandleFace ZPos;
|
||||
static HandleFace ZNeg;
|
||||
static std::array<HandleFace, 6> Faces;
|
||||
};
|
||||
|
||||
enum HandlesType {
|
||||
MoveHandles,
|
||||
ScaleHandles,
|
||||
RotateHandles,
|
||||
};
|
||||
|
||||
class DEF_INST_(abstract) Handles : public Instance {
|
||||
AUTOGEN_PREAMBLE
|
||||
public:
|
||||
bool nixAxes = false; // XYZ -> ZXY, used with rotation
|
||||
bool active;
|
||||
std::weak_ptr<Part> adornee;
|
||||
HandlesType handlesType;
|
||||
|
||||
// inline std::weak_ptr<Part> GetAdornee() { return adornee; }
|
||||
// inline void SetAdornee(std::weak_ptr<Part> newAdornee) { this->adornee = newAdornee; updateAdornee(); };
|
||||
|
||||
Handles();
|
||||
|
||||
// World-space handles vs local-space handles
|
||||
bool worldMode = false;
|
||||
CFrame GetCFrameOfHandle(HandleFace face);
|
||||
CFrame PartCFrameFromHandlePos(HandleFace face, Vector3 newPos);
|
||||
Vector3 HandleSize(HandleFace face);
|
||||
std::optional<HandleFace> RaycastHandle(rp3d::Ray ray);
|
||||
|
||||
static inline std::shared_ptr<Handles> New() { return std::make_shared<Handles>(); };
|
||||
};
|
|
@ -16,7 +16,8 @@
|
|||
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/color3.h"
|
||||
#include "objects/handles.h"
|
||||
#include "handles.h"
|
||||
#include "math_helper.h"
|
||||
#include "rendering/torus.h"
|
||||
#include "shader.h"
|
||||
#include "mesh.h"
|
||||
|
@ -265,8 +266,10 @@ void renderSkyBox() {
|
|||
glDrawArrays(GL_TRIANGLES, 0, 36);
|
||||
}
|
||||
|
||||
static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
|
||||
|
||||
void renderHandles() {
|
||||
if (editorToolHandles->adornee.expired() || !editorToolHandles->active) return;
|
||||
if (!editorToolHandles.active) return;
|
||||
|
||||
glDepthMask(GL_TRUE);
|
||||
glCullFace(GL_BACK);
|
||||
|
@ -292,18 +295,18 @@ void renderHandles() {
|
|||
handleShader->set("viewPos", camera.cameraPos);
|
||||
|
||||
for (auto face : HandleFace::Faces) {
|
||||
glm::mat4 model = editorToolHandles->GetCFrameOfHandle(face);
|
||||
model = glm::scale(model, (glm::vec3)editorToolHandles->HandleSize(face));
|
||||
glm::mat4 model = getHandleCFrame(face);
|
||||
model = glm::scale(model, (glm::vec3)handleSize(face));
|
||||
handleShader->set("model", model);
|
||||
handleShader->set("material", Material {
|
||||
.diffuse = glm::abs(face.normal),
|
||||
.diffuse = editorToolHandles.handlesType == HandlesType::RotateHandles ? (glm::vec3)(XYZToZXY * glm::abs(face.normal)) : glm::abs(face.normal),
|
||||
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
|
||||
.shininess = 16.0f,
|
||||
});
|
||||
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
|
||||
handleShader->set("normalMatrix", normalMatrix);
|
||||
|
||||
if (editorToolHandles->handlesType == HandlesType::MoveHandles) {
|
||||
if (editorToolHandles.handlesType == HandlesType::MoveHandles) {
|
||||
ARROW_MESH->bind();
|
||||
glDrawArrays(GL_TRIANGLES, 0, ARROW_MESH->vertexCount);
|
||||
} else {
|
||||
|
@ -319,7 +322,7 @@ void renderHandles() {
|
|||
identityShader->set("aColor", glm::vec3(0.f, 1.f, 1.f));
|
||||
|
||||
for (auto face : HandleFace::Faces) {
|
||||
CFrame cframe = editorToolHandles->GetCFrameOfHandle(face);
|
||||
CFrame cframe = getHandleCFrame(face);
|
||||
glm::vec4 screenPos = projection * view * glm::vec4((glm::vec3)cframe.Position(), 1.0f);
|
||||
|
||||
if (screenPos.z < 0) continue;
|
||||
|
@ -437,16 +440,27 @@ void renderOutlines() {
|
|||
|
||||
// Pass in the camera position
|
||||
outlineShader->set("viewPos", camera.cameraPos);
|
||||
outlineShader->set("thickness", 0.4f);
|
||||
|
||||
// outlineShader->set("color", glm::vec3(1.f, 0.f, 0.f));
|
||||
|
||||
// Sort by nearest
|
||||
glm::vec3 min, max;
|
||||
bool first = true;
|
||||
|
||||
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
|
||||
InstanceRef inst = *it;
|
||||
if (inst->GetClass() != &Part::TYPE) continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
if (!part->selected) continue;
|
||||
|
||||
if (first)
|
||||
min = part->position(), max = part->position();
|
||||
first = false;
|
||||
|
||||
Vector3 aabbSize = part->GetAABB();
|
||||
expandAABB(min, max, part->position() - aabbSize / 2.f);
|
||||
expandAABB(min, max, part->position() + aabbSize / 2.f);
|
||||
|
||||
glm::mat4 model = part->cframe;
|
||||
model = glm::scale(model, (glm::vec3)part->size + glm::vec3(0.2));
|
||||
outlineShader->set("model", model);
|
||||
|
@ -455,10 +469,26 @@ void renderOutlines() {
|
|||
OUTLINE_MESH->bind();
|
||||
glDrawArrays(GL_TRIANGLES, 0, OUTLINE_MESH->vertexCount);
|
||||
}
|
||||
|
||||
// Render AABB of selected parts
|
||||
if (first) return;
|
||||
|
||||
glm::vec3 outlineSize, outlinePos;
|
||||
outlineSize = (max - min);
|
||||
outlinePos = (max + min) / 2.f;
|
||||
|
||||
glm::mat4 model = glm::translate(glm::mat4(1.0f), outlinePos);
|
||||
model = glm::scale(model, outlineSize + glm::vec3(0.1));
|
||||
outlineShader->set("model", model);
|
||||
outlineShader->set("scale", outlineSize + glm::vec3(0.05));
|
||||
outlineShader->set("thickness", 0.2f);
|
||||
|
||||
OUTLINE_MESH->bind();
|
||||
glDrawArrays(GL_TRIANGLES, 0, OUTLINE_MESH->vertexCount);
|
||||
}
|
||||
|
||||
void renderRotationArcs() {
|
||||
if (editorToolHandles->adornee.expired() || editorToolHandles->handlesType != HandlesType::RotateHandles) return;
|
||||
if (!editorToolHandles.active || editorToolHandles.handlesType != HandlesType::RotateHandles) return;
|
||||
|
||||
glDepthMask(GL_TRUE);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <qsoundeffect.h>
|
||||
#include <string>
|
||||
#include "mainglwidget.h"
|
||||
#include "handles.h"
|
||||
#include "logger.h"
|
||||
#include "mainwindow.h"
|
||||
#include "common.h"
|
||||
|
@ -171,21 +172,21 @@ inline glm::vec3 vec3fy(glm::vec4 vec) {
|
|||
|
||||
// Taken from Godot's implementation of moving handles (godot/editor/plugins/gizmos/gizmo_3d_helper.cpp)
|
||||
void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
|
||||
if (!isMouseDragging || !draggingHandle|| editorToolHandles->adornee.expired() || !editorToolHandles->active) return;
|
||||
if (!isMouseDragging || !draggingHandle|| !editorToolHandles.active) return;
|
||||
|
||||
QPoint position = evt->pos();
|
||||
|
||||
auto part = editorToolHandles->adornee.lock();
|
||||
auto part = getHandleAdornee();
|
||||
|
||||
// This was actually quite a difficult problem to solve, managing to get the handle to go underneath the cursor
|
||||
|
||||
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
||||
pointDir = glm::normalize(pointDir);
|
||||
|
||||
CFrame handleCFrame = editorToolHandles->GetCFrameOfHandle(draggingHandle.value());
|
||||
CFrame handleCFrame = getHandleCFrame(draggingHandle.value());
|
||||
|
||||
// Current frame. Identity frame if worldMode == true, selected object's frame if worldMode == false
|
||||
CFrame frame = editorToolHandles->worldMode ? CFrame::IDENTITY + part->position() : part->cframe.Rotation();
|
||||
CFrame frame = editorToolHandles.worldMode ? CFrame::IDENTITY + part->position() : part->cframe.Rotation();
|
||||
|
||||
// Segment from axis stretching -4096 to +4096 rel to handle's position
|
||||
glm::vec3 axisSegment0 = handleCFrame.Position() + (-handleCFrame.LookVector() * 4096.0f);
|
||||
|
@ -200,7 +201,7 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
|
|||
get_closest_points_between_segments(axisSegment0, axisSegment1, mouseSegment0, mouseSegment1, handlePoint, rb);
|
||||
|
||||
// Find new part position
|
||||
glm::vec3 centerPoint = editorToolHandles->PartCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position();
|
||||
glm::vec3 centerPoint = partCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position();
|
||||
|
||||
// Apply snapping in the current frame
|
||||
glm::vec3 diff = centerPoint - (glm::vec3)part->position();
|
||||
|
@ -258,10 +259,10 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
|
|||
// Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490)
|
||||
glm::vec2 startPoint;
|
||||
void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
|
||||
if (!isMouseDragging || !draggingHandle || editorToolHandles->adornee.expired() || !editorToolHandles->active) return;
|
||||
if (!isMouseDragging || !draggingHandle || !editorToolHandles.active) return;
|
||||
|
||||
glm::vec2 destPoint = glm::vec2(evt->pos().x(), evt->pos().y());
|
||||
auto part = editorToolHandles->adornee.lock();
|
||||
auto part = getHandleAdornee();
|
||||
|
||||
// Calculate part pos as screen point
|
||||
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)width() / (float)height(), 0.1f, 1000.0f);
|
||||
|
@ -282,14 +283,16 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
|
|||
if (snappingFactor() > 0)
|
||||
angle = roundf(angle * 4 / PI / snappingFactor()) / 4 * PI * snappingFactor();
|
||||
|
||||
glm::vec3 handleNormal = XYZToZXY * glm::abs(draggingHandle->normal);
|
||||
|
||||
// Checks if the rotation axis is facing towards, or away from the camera
|
||||
// If it pointing away from the camera, then we need to invert the angle change
|
||||
glm::vec4 rotationAxis = projection * view * glm::vec4((glm::vec3)(initialFrame * glm::abs(draggingHandle->normal)), 1.f);
|
||||
glm::vec4 rotationAxis = projection * view * glm::vec4((glm::vec3)(initialFrame * handleNormal), 1.f);
|
||||
rotationAxis /= rotationAxis.w;
|
||||
glm::vec4 signVec = glm::normalize(rotationAxis - partCenterRaw);
|
||||
float sign = -glm::sign(signVec.z);
|
||||
|
||||
glm::vec3 angles = glm::abs(draggingHandle->normal) * sign * glm::vec3(angle);
|
||||
glm::vec3 angles = handleNormal * sign * glm::vec3(angle);
|
||||
|
||||
part->cframe = initialFrame * CFrame::FromEulerAnglesXYZ(-angles);
|
||||
|
||||
|
@ -299,8 +302,8 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
|
|||
}
|
||||
|
||||
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
|
||||
if (editorToolHandles->adornee.expired() || !editorToolHandles->active) return std::nullopt;
|
||||
return editorToolHandles->RaycastHandle(rp3d::Ray(glmToRp(camera.cameraPos), glmToRp(glm::normalize(pointDir)) * 50000));
|
||||
if (!editorToolHandles.active) return std::nullopt;
|
||||
return ::raycastHandle(rp3d::Ray(glmToRp(camera.cameraPos), glmToRp(glm::normalize(pointDir)) * 50000));
|
||||
}
|
||||
|
||||
void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
|
||||
|
@ -363,7 +366,7 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
|||
auto handle = raycastHandle(pointDir);
|
||||
if (handle.has_value()) {
|
||||
startPoint = glm::vec2(evt->pos().x(), evt->pos().y());
|
||||
initialFrame = editorToolHandles->adornee.lock()->cframe;
|
||||
initialFrame = getHandleAdornee()->cframe;
|
||||
isMouseDragging = true;
|
||||
draggingHandle = handle;
|
||||
return;
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
bool worldSpaceTransforms = false;
|
||||
|
||||
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;
|
||||
|
@ -62,7 +63,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
ui->setupUi(this);
|
||||
setMouseTracking(true);
|
||||
|
||||
// https://stackoverflow.com/a/78854851/16255372
|
||||
QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() + QStringList { "./assets/icons" });
|
||||
if (isDarkMode())
|
||||
QIcon::setFallbackThemeName("editor-dark");
|
||||
|
@ -100,16 +100,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
});
|
||||
|
||||
connectActionHandlers();
|
||||
|
||||
// Update handles
|
||||
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
|
||||
editorToolHandles->adornee = {};
|
||||
if (newSelection.size() == 0) return;
|
||||
InstanceRef inst = newSelection[0].lock();
|
||||
if (inst->GetClass() != &Part::TYPE) return;
|
||||
|
||||
editorToolHandles->adornee = std::dynamic_pointer_cast<Part>(inst);
|
||||
});
|
||||
|
||||
// Update properties
|
||||
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
|
||||
|
@ -456,11 +446,11 @@ void MainWindow::updateToolbars() {
|
|||
ui->actionGridSnap05->setChecked(snappingMode == GridSnappingMode::SNAP_05_STUDS);
|
||||
ui->actionGridSnapOff->setChecked(snappingMode == GridSnappingMode::SNAP_OFF);
|
||||
|
||||
editorToolHandles->worldMode = (selectedTool == TOOL_SCALE || selectedTool == TOOL_ROTATE) ? false : worldSpaceTransforms;
|
||||
editorToolHandles->nixAxes = selectedTool == TOOL_ROTATE;
|
||||
editorToolHandles.worldMode = (selectedTool == TOOL_SCALE || selectedTool == TOOL_ROTATE) ? false : worldSpaceTransforms;
|
||||
editorToolHandles.nixAxes = selectedTool == TOOL_ROTATE;
|
||||
|
||||
editorToolHandles->active = selectedTool > TOOL_SELECT && selectedTool < TOOL_SMOOTH;
|
||||
editorToolHandles->handlesType =
|
||||
editorToolHandles.active = selectedTool > TOOL_SELECT && selectedTool < TOOL_SMOOTH;
|
||||
editorToolHandles.handlesType =
|
||||
selectedTool == TOOL_MOVE ? HandlesType::MoveHandles
|
||||
: selectedTool == TOOL_SCALE ? HandlesType::ScaleHandles
|
||||
: selectedTool == TOOL_ROTATE ? HandlesType::RotateHandles
|
||||
|
|
|
@ -680,7 +680,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="surface-weld"/>
|
||||
<iconset theme="surface-hinge"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Hinge</string>
|
||||
|
@ -697,7 +697,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="surface-weld"/>
|
||||
<iconset theme="surface-motor"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Motor</string>
|
||||
|
|
|
@ -133,5 +133,5 @@ void PlaceDocument::init() {
|
|||
std::shared_ptr<Script> script = Script::New();
|
||||
gWorkspace()->AddChild(script);
|
||||
MainWindow* mainWnd = dynamic_cast<MainWindow*>(window());
|
||||
mainWnd->openScriptDocument(script);
|
||||
// mainWnd->openScriptDocument(script);
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
#include <Qsci/qscilexerlua.h>
|
||||
#include <Qsci/qsciscintillabase.h>
|
||||
#include <Qsci/qscistyle.h>
|
||||
#include <Qsci/qsciapis.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <qboxlayout.h>
|
||||
|
@ -12,9 +13,27 @@
|
|||
#include <qdebug.h>
|
||||
#include <qglobal.h>
|
||||
#include <qlayout.h>
|
||||
#include <qtextformat.h>
|
||||
#include "mainwindow.h"
|
||||
#include "objects/script.h"
|
||||
#include "datatypes/meta.h"
|
||||
#include <QPalette>
|
||||
#include <QStyleHints>
|
||||
|
||||
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")},
|
||||
|
@ -35,6 +54,61 @@ std::map<int, const QColor> DARK_MODE_COLOR_SCHEME = {{
|
|||
|
||||
}};
|
||||
|
||||
class ObLuaLexer : public QsciLexerLua {
|
||||
const char * keywords(int set) const override {
|
||||
// Taken from qscilexerlua.cpp
|
||||
|
||||
if (set == 1)
|
||||
// Keywords.
|
||||
return
|
||||
"and break do else elseif end false for function if "
|
||||
"in local nil not or repeat return then true until "
|
||||
"while";
|
||||
|
||||
if (set == 2)
|
||||
// Basic functions.
|
||||
return
|
||||
//"foreach foreachi getn "
|
||||
|
||||
// Openblocks extensions
|
||||
"shared require game workspace "
|
||||
|
||||
"_G _VERSION getfenv getmetatable ipairs loadstring "
|
||||
"next pairs pcall rawequal rawget rawset select "
|
||||
"setfenv setmetatable xpcall string table tonumber "
|
||||
"tostring type math newproxy coroutine io os";
|
||||
|
||||
if (set == 3)
|
||||
// String, table and maths functions.
|
||||
return
|
||||
// "abs acos asin atan atan2 ceil cos deg exp floor "
|
||||
// "format frexp gsub ldexp log log10 max min mod rad "
|
||||
// "random randomseed sin sqrt tan "
|
||||
|
||||
"string.byte string.char string.dump string.find "
|
||||
"string.len string.lower string.rep string.sub "
|
||||
"string.upper string.format string.gfind string.gsub "
|
||||
"table.concat table.foreach table.foreachi table.getn "
|
||||
"table.sort table.insert table.remove table.setn "
|
||||
"math.abs math.acos math.asin math.atan math.atan2 "
|
||||
"math.ceil math.cos math.deg math.exp math.floor "
|
||||
"math.frexp math.ldexp math.log math.log10 math.max "
|
||||
"math.min math.mod math.pi math.rad math.random "
|
||||
"math.randomseed math.sin math.sqrt math.tan";
|
||||
|
||||
if (set == 4)
|
||||
// Coroutine, I/O and system facilities.
|
||||
return
|
||||
// "clock date difftime time "
|
||||
|
||||
"coroutine.create coroutine.resume coroutine.status "
|
||||
"coroutine.wrap coroutine.yield os.clock os.date "
|
||||
"os.difftime os.time";
|
||||
|
||||
return 0;
|
||||
};
|
||||
};
|
||||
|
||||
ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
|
||||
script(script), QMdiSubWindow(parent) {
|
||||
|
||||
|
@ -49,18 +123,24 @@ ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
|
|||
dynamic_cast<MainWindow*>(window())->closeScriptDocument(script);
|
||||
});
|
||||
|
||||
// QFrame* frame = new QFrame;
|
||||
// QVBoxLayout* frameLayout = new QVBoxLayout;
|
||||
// frame->setLayout(frameLayout);
|
||||
scintilla = new QsciScintilla;
|
||||
// frameLayout->addWidget(scintilla);
|
||||
// setWidget(frame);
|
||||
setWidget(scintilla);
|
||||
QFrame* frame = new QFrame;
|
||||
QVBoxLayout* frameLayout = new QVBoxLayout;
|
||||
frameLayout->setMargin(0);
|
||||
frame->setLayout(frameLayout);
|
||||
scintilla = new QsciScintilla(this);
|
||||
|
||||
frameLayout->addWidget(scintilla);
|
||||
setWidget(frame);
|
||||
|
||||
// https://forum.qt.io/post/803690
|
||||
QFont findFont("<NONE>");
|
||||
findFont.setStyleHint(QFont::Monospace);
|
||||
QFontInfo info(findFont);
|
||||
|
||||
QFont font;
|
||||
font.setFamily("Consolas");
|
||||
font.setStyleHint(QFont::Monospace);
|
||||
font.setPointSize(12);
|
||||
font.setFamily(info.family());
|
||||
font.setPointSize(10);
|
||||
font.setFixedPitch(true);
|
||||
|
||||
// scintilla->setMargins(2);
|
||||
scintilla->setScrollWidth(1); // Hide scrollbars on empty document, it will grow automatically
|
||||
|
@ -74,14 +154,27 @@ ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
|
|||
|
||||
scintilla->setText(QString::fromStdString(script->source));
|
||||
|
||||
QsciLexerLua* lexer = new QsciLexerLua;
|
||||
ObLuaLexer* lexer = new ObLuaLexer;
|
||||
lexer->setFont(font);
|
||||
scintilla->setLexer(lexer);
|
||||
|
||||
// Remove background highlight color
|
||||
lexer->setPaper(palette().light().color());
|
||||
QsciAPIs* api = makeApis(lexer);
|
||||
lexer->setAPIs(api);
|
||||
scintilla->setAutoCompletionSource(QsciScintilla::AcsAPIs);
|
||||
scintilla->setAutoCompletionThreshold(1);
|
||||
scintilla->setAutoCompletionCaseSensitivity(false);
|
||||
scintilla->setAutoCompletionReplaceWord(false);
|
||||
scintilla->setCallTipsVisible(-1);
|
||||
scintilla->setCallTipsPosition(QsciScintilla::CallTipsBelowText);
|
||||
|
||||
// Set color scheme
|
||||
// https://stackoverflow.com/a/26318796/16255372
|
||||
for (auto& [style, color] : DARK_MODE_COLOR_SCHEME) {
|
||||
lexer->setColor(color, style);
|
||||
if (isDarkMode()) {
|
||||
for (auto& [style, color] : DARK_MODE_COLOR_SCHEME) {
|
||||
lexer->setColor(color, style);
|
||||
}
|
||||
}
|
||||
|
||||
// lexer->setAutoIndentStyle(QsciScintilla::AiOpening | QsciScintilla::AiMaintain | QsciScintilla::AiClosing);
|
||||
|
@ -96,3 +189,15 @@ ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
|
|||
|
||||
ScriptDocument::~ScriptDocument() {
|
||||
}
|
||||
|
||||
QsciAPIs* makeApis(QsciLexer* lexer) {
|
||||
QsciAPIs* apis = new QsciAPIs(lexer);
|
||||
|
||||
apis->add("workspace");
|
||||
apis->add("game");
|
||||
apis->add("wait(seconds: number)\n\nPauses the current thread for the specified number of seconds");
|
||||
|
||||
apis->prepare();
|
||||
|
||||
return apis;
|
||||
}
|