mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-05 11:01:49 +00:00
Merge branch 'main' into continue-sdl-support-implementation
This commit is contained in:
commit
7f7533f33e
214 changed files with 5299 additions and 1731 deletions
|
|
@ -112,7 +112,7 @@ target_compile_options(
|
|||
-Wno-sign-compare
|
||||
-Wno-unknown-pragmas
|
||||
>
|
||||
$<$<CXX_COMPILER_ID:Clang>:
|
||||
$<$<CXX_COMPILER_ID:GNU>:
|
||||
-Wduplicated-branches
|
||||
-Wduplicated-cond
|
||||
>)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include "content/Content.hpp"
|
||||
#include "content/ContentPack.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
#include "coders/vec3.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "frontend/UiDocument.hpp"
|
||||
#include "graphics/core/Atlas.hpp"
|
||||
|
|
@ -184,7 +184,7 @@ assetload::postfunc assetload::atlas(
|
|||
if (!append_atlas(builder, file)) continue;
|
||||
}
|
||||
std::set<std::string> names = builder.getNames();
|
||||
Atlas* atlas = builder.build(2, false).release();
|
||||
Atlas* atlas = builder.build(ATLAS_EXTRUSION, false).release();
|
||||
return [=](auto assets) {
|
||||
atlas->prepare();
|
||||
assets->store(std::unique_ptr<Atlas>(atlas), name);
|
||||
|
|
@ -501,7 +501,7 @@ static bool load_animation(
|
|||
}
|
||||
if (!append_atlas(builder, file)) continue;
|
||||
}
|
||||
auto srcAtlas = builder.build(2, true);
|
||||
auto srcAtlas = builder.build(ATLAS_EXTRUSION, true);
|
||||
if (frameList.empty()) {
|
||||
for (const auto& frameName : builder.getNames()) {
|
||||
frameList.emplace_back(frameName, 0);
|
||||
|
|
|
|||
|
|
@ -5,11 +5,41 @@
|
|||
|
||||
#include "debug/Logger.hpp"
|
||||
#include "alutil.hpp"
|
||||
#include "../MemoryPCMStream.hpp"
|
||||
|
||||
static debug::Logger logger("al-audio");
|
||||
|
||||
using namespace audio;
|
||||
|
||||
const char* alc_error_to_string(ALCenum error) {
|
||||
switch (error) {
|
||||
case ALC_NO_ERROR:
|
||||
return "no error";
|
||||
case ALC_INVALID_DEVICE:
|
||||
return "invalid device handle";
|
||||
case ALC_INVALID_CONTEXT:
|
||||
return "invalid context handle";
|
||||
case ALC_INVALID_ENUM:
|
||||
return "invalid enum parameter passed to an ALC call";
|
||||
case ALC_INVALID_VALUE:
|
||||
return "invalid value parameter passed to an ALC call";
|
||||
case ALC_OUT_OF_MEMORY:
|
||||
return "out of memory";
|
||||
default:
|
||||
return "unknown ALC error";
|
||||
}
|
||||
}
|
||||
|
||||
static bool check_alc_errors(ALCdevice* device, const char* context) {
|
||||
ALCenum error = alcGetError(device);
|
||||
if (error == ALC_NO_ERROR) {
|
||||
return false;
|
||||
}
|
||||
logger.error() << context << ": " << alc_error_to_string(error) << "("
|
||||
<< error << ")";
|
||||
return true;
|
||||
}
|
||||
|
||||
ALSound::ALSound(
|
||||
ALAudio* al, uint buffer, const std::shared_ptr<PCM>& pcm, bool keepPCM
|
||||
)
|
||||
|
|
@ -37,6 +67,70 @@ std::unique_ptr<Speaker> ALSound::newInstance(int priority, int channel) const {
|
|||
return speaker;
|
||||
}
|
||||
|
||||
ALInputDevice::ALInputDevice(
|
||||
ALAudio* al,
|
||||
ALCdevice* device,
|
||||
uint channels,
|
||||
uint bitsPerSample,
|
||||
uint sampleRate
|
||||
)
|
||||
: al(al),
|
||||
device(device),
|
||||
channels(channels),
|
||||
bitsPerSample(bitsPerSample),
|
||||
sampleRate(sampleRate) {
|
||||
const ALCchar* deviceName = alcGetString(device, ALC_CAPTURE_DEVICE_SPECIFIER);
|
||||
|
||||
if (deviceName) {
|
||||
deviceSpecifier = std::string(deviceName);
|
||||
} else {
|
||||
logger.warning() << "could not retrieve input device specifier";
|
||||
}
|
||||
}
|
||||
|
||||
ALInputDevice::~ALInputDevice() {
|
||||
alcCaptureCloseDevice(device);
|
||||
check_alc_errors(device, "alcCaptureCloseDevice");
|
||||
}
|
||||
|
||||
void ALInputDevice::startCapture() {
|
||||
alcCaptureStart(device);
|
||||
check_alc_errors(device, "alcCaptureStart");
|
||||
}
|
||||
|
||||
void ALInputDevice::stopCapture() {
|
||||
alcCaptureStop(device);
|
||||
check_alc_errors(device, "alcCaptureStop");
|
||||
}
|
||||
|
||||
uint ALInputDevice::getChannels() const {
|
||||
return channels;
|
||||
}
|
||||
|
||||
uint ALInputDevice::getSampleRate() const {
|
||||
return sampleRate;
|
||||
}
|
||||
|
||||
uint ALInputDevice::getBitsPerSample() const {
|
||||
return bitsPerSample;
|
||||
}
|
||||
|
||||
const std::string& ALInputDevice::getDeviceSpecifier() const {
|
||||
return deviceSpecifier;
|
||||
}
|
||||
|
||||
size_t ALInputDevice::read(char* buffer, size_t bufferSize) {
|
||||
ALCint samplesCount = 0;
|
||||
alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, sizeof(samplesCount), &samplesCount);
|
||||
check_alc_errors(device, "alcGetIntegerv(ALC_CAPTURE_SAMPLES)");
|
||||
size_t samplesRead = std::min<ALCsizei>(
|
||||
samplesCount, bufferSize / channels / (bitsPerSample >> 3)
|
||||
);
|
||||
alcCaptureSamples(device, buffer, samplesRead);
|
||||
check_alc_errors(device, "alcCaptureSamples");
|
||||
return samplesRead * channels * (bitsPerSample >> 3);
|
||||
}
|
||||
|
||||
ALStream::ALStream(
|
||||
ALAudio* al, std::shared_ptr<PCMStream> source, bool keepSource
|
||||
)
|
||||
|
|
@ -81,9 +175,10 @@ std::unique_ptr<Speaker> ALStream::createSpeaker(bool loop, int channel) {
|
|||
for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) {
|
||||
uint free_buffer = al->getFreeBuffer();
|
||||
if (!preloadBuffer(free_buffer, loop)) {
|
||||
break;
|
||||
unusedBuffers.push(free_buffer);
|
||||
} else {
|
||||
AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer));
|
||||
}
|
||||
AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer));
|
||||
}
|
||||
return std::make_unique<ALSpeaker>(al, free_source, PRIORITY_HIGH, channel);
|
||||
}
|
||||
|
|
@ -130,11 +225,11 @@ void ALStream::unqueueBuffers(uint alsource) {
|
|||
uint ALStream::enqueueBuffers(uint alsource) {
|
||||
uint preloaded = 0;
|
||||
if (!unusedBuffers.empty()) {
|
||||
uint first_buffer = unusedBuffers.front();
|
||||
if (preloadBuffer(first_buffer, loop)) {
|
||||
uint firstBuffer = unusedBuffers.front();
|
||||
if (preloadBuffer(firstBuffer, loop)) {
|
||||
preloaded++;
|
||||
unusedBuffers.pop();
|
||||
AL_CHECK(alSourceQueueBuffers(alsource, 1, &first_buffer));
|
||||
AL_CHECK(alSourceQueueBuffers(alsource, 1, &firstBuffer));
|
||||
}
|
||||
}
|
||||
return preloaded;
|
||||
|
|
@ -144,14 +239,14 @@ void ALStream::update(double delta) {
|
|||
if (this->speaker == 0) {
|
||||
return;
|
||||
}
|
||||
auto p_speaker = audio::get_speaker(this->speaker);
|
||||
if (p_speaker == nullptr) {
|
||||
auto speaker = audio::get_speaker(this->speaker);
|
||||
if (speaker == nullptr) {
|
||||
this->speaker = 0;
|
||||
return;
|
||||
}
|
||||
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(p_speaker);
|
||||
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(speaker);
|
||||
assert(alspeaker != nullptr);
|
||||
if (alspeaker->stopped) {
|
||||
if (alspeaker->manuallyStopped) {
|
||||
this->speaker = 0;
|
||||
return;
|
||||
}
|
||||
|
|
@ -162,11 +257,11 @@ void ALStream::update(double delta) {
|
|||
uint preloaded = enqueueBuffers(alsource);
|
||||
|
||||
// alspeaker->stopped is assigned to false at ALSpeaker::play(...)
|
||||
if (p_speaker->isStopped() && !alspeaker->stopped) { //TODO: -V560 false-positive?
|
||||
if (speaker->isStopped() && !alspeaker->manuallyStopped) { //TODO: -V560 false-positive?
|
||||
if (preloaded) {
|
||||
p_speaker->play();
|
||||
} else {
|
||||
p_speaker->stop();
|
||||
speaker->play();
|
||||
} else if (isStopOnEnd()){
|
||||
speaker->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -207,6 +302,14 @@ void ALStream::setTime(duration_t time) {
|
|||
}
|
||||
}
|
||||
|
||||
bool ALStream::isStopOnEnd() const {
|
||||
return stopOnEnd;
|
||||
}
|
||||
|
||||
void ALStream::setStopOnEnd(bool flag) {
|
||||
stopOnEnd = flag;
|
||||
}
|
||||
|
||||
ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel)
|
||||
: al(al), priority(priority), channel(channel), source(source) {
|
||||
}
|
||||
|
|
@ -273,7 +376,7 @@ void ALSpeaker::setLoop(bool loop) {
|
|||
|
||||
void ALSpeaker::play() {
|
||||
paused = false;
|
||||
stopped = false;
|
||||
manuallyStopped = false;
|
||||
auto p_channel = get_channel(this->channel);
|
||||
AL_CHECK(alSourcef(
|
||||
source,
|
||||
|
|
@ -289,7 +392,7 @@ void ALSpeaker::pause() {
|
|||
}
|
||||
|
||||
void ALSpeaker::stop() {
|
||||
stopped = true;
|
||||
manuallyStopped = true;
|
||||
if (source) {
|
||||
AL_CHECK(alSourceStop(source));
|
||||
|
||||
|
|
@ -353,6 +456,13 @@ int ALSpeaker::getPriority() const {
|
|||
return priority;
|
||||
}
|
||||
|
||||
|
||||
bool ALSpeaker::isManuallyStopped() const {
|
||||
return manuallyStopped;
|
||||
}
|
||||
|
||||
static bool alc_enumeration_ext = false;
|
||||
|
||||
ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
||||
: device(device), context(context) {
|
||||
ALCint size;
|
||||
|
|
@ -365,9 +475,15 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
|||
maxSources = attrs[i + 1];
|
||||
}
|
||||
}
|
||||
auto devices = getAvailableDevices();
|
||||
logger.info() << "devices:";
|
||||
for (auto& name : devices) {
|
||||
auto outputDevices = getOutputDeviceNames();
|
||||
logger.info() << "output devices:";
|
||||
for (auto& name : outputDevices) {
|
||||
logger.info() << " " << name;
|
||||
}
|
||||
|
||||
auto inputDevices = getInputDeviceNames();
|
||||
logger.info() << "input devices:";
|
||||
for (auto& name : inputDevices) {
|
||||
logger.info() << " " << name;
|
||||
}
|
||||
}
|
||||
|
|
@ -385,8 +501,10 @@ ALAudio::~ALAudio() {
|
|||
AL_CHECK(alDeleteBuffers(1, &buffer));
|
||||
}
|
||||
|
||||
AL_CHECK(alcMakeContextCurrent(context));
|
||||
alcMakeContextCurrent(nullptr);
|
||||
check_alc_errors(device, "alcMakeContextCurrent");
|
||||
alcDestroyContext(context);
|
||||
check_alc_errors(device, "alcDestroyContext");
|
||||
if (!alcCloseDevice(device)) {
|
||||
logger.error() << "device not closed!";
|
||||
}
|
||||
|
|
@ -411,7 +529,71 @@ std::unique_ptr<Stream> ALAudio::openStream(
|
|||
return std::make_unique<ALStream>(this, stream, keepSource);
|
||||
}
|
||||
|
||||
std::vector<std::string> ALAudio::getInputDeviceNames() {
|
||||
std::vector<std::string> devices;
|
||||
|
||||
if (!alc_enumeration_ext) {
|
||||
logger.warning() << "enumeration extension is not available";
|
||||
return devices;
|
||||
}
|
||||
|
||||
auto deviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
|
||||
if (deviceList == nullptr) {
|
||||
logger.warning() << "no input devices found";
|
||||
return devices;
|
||||
}
|
||||
while (*deviceList) {
|
||||
std::string deviceName(deviceList);
|
||||
devices.push_back(deviceName);
|
||||
deviceList += deviceName.length() + 1;
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
std::vector<std::string> ALAudio::getOutputDeviceNames() {
|
||||
std::vector<std::string> devices;
|
||||
|
||||
if (!alc_enumeration_ext) {
|
||||
logger.warning() << "enumeration extension is not available";
|
||||
return devices;
|
||||
}
|
||||
|
||||
auto deviceList = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
|
||||
if (deviceList == nullptr) {
|
||||
logger.warning() << "no input devices found";
|
||||
return devices;
|
||||
}
|
||||
while (*deviceList) {
|
||||
std::string deviceName(deviceList);
|
||||
devices.push_back(deviceName);
|
||||
deviceList += deviceName.length() + 1;
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
std::unique_ptr<InputDevice> ALAudio::openInputDevice(
|
||||
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
|
||||
) {
|
||||
uint bps = bitsPerSample >> 3;
|
||||
ALCdevice* device = alcCaptureOpenDevice(
|
||||
deviceName.empty() ? nullptr : deviceName.c_str(),
|
||||
sampleRate,
|
||||
AL::to_al_format(channels, bitsPerSample),
|
||||
sampleRate * channels * bps / 8
|
||||
);
|
||||
if (check_alc_errors(device, "alcCaptureOpenDevice"))
|
||||
return nullptr;
|
||||
|
||||
return std::make_unique<ALInputDevice>(
|
||||
this, device, channels, bitsPerSample, sampleRate
|
||||
);
|
||||
}
|
||||
|
||||
std::unique_ptr<ALAudio> ALAudio::create() {
|
||||
alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT");
|
||||
|
||||
ALCdevice* device = alcOpenDevice(nullptr);
|
||||
if (device == nullptr) return nullptr;
|
||||
ALCcontext* context = alcCreateContext(device, nullptr);
|
||||
|
|
@ -468,24 +650,6 @@ void ALAudio::freeBuffer(uint buffer) {
|
|||
freebuffers.push_back(buffer);
|
||||
}
|
||||
|
||||
std::vector<std::string> ALAudio::getAvailableDevices() const {
|
||||
std::vector<std::string> devicesVec;
|
||||
|
||||
const ALCchar* devices;
|
||||
devices = alcGetString(device, ALC_DEVICE_SPECIFIER);
|
||||
if (!AL_GET_ERROR()) {
|
||||
return devicesVec;
|
||||
}
|
||||
|
||||
const char* ptr = devices;
|
||||
do {
|
||||
devicesVec.emplace_back(ptr);
|
||||
ptr += devicesVec.back().size() + 1;
|
||||
} while (ptr[0]);
|
||||
|
||||
return devicesVec;
|
||||
}
|
||||
|
||||
void ALAudio::setListener(
|
||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ namespace audio {
|
|||
bool keepSource;
|
||||
char buffer[BUFFER_SIZE];
|
||||
bool loop = false;
|
||||
bool stopOnEnd = false;
|
||||
|
||||
bool preloadBuffer(uint buffer, bool loop);
|
||||
void unqueueBuffers(uint alsource);
|
||||
|
|
@ -80,6 +81,39 @@ namespace audio {
|
|||
void setTime(duration_t time) override;
|
||||
|
||||
static inline constexpr uint STREAM_BUFFERS = 3;
|
||||
|
||||
bool isStopOnEnd() const override;
|
||||
|
||||
void setStopOnEnd(bool stopOnEnd) override;
|
||||
};
|
||||
|
||||
class ALInputDevice : public InputDevice {
|
||||
public:
|
||||
ALInputDevice(
|
||||
ALAudio* al,
|
||||
ALCdevice* device,
|
||||
uint channels,
|
||||
uint bitsPerSample,
|
||||
uint sampleRate
|
||||
);
|
||||
~ALInputDevice() override;
|
||||
|
||||
void startCapture() override;
|
||||
void stopCapture() override;
|
||||
|
||||
uint getChannels() const override;
|
||||
uint getSampleRate() const override;
|
||||
uint getBitsPerSample() const override;
|
||||
const std::string& getDeviceSpecifier() const override;
|
||||
|
||||
size_t read(char* buffer, size_t bufferSize) override;
|
||||
private:
|
||||
ALAudio* al;
|
||||
ALCdevice* device;
|
||||
uint channels;
|
||||
uint bitsPerSample;
|
||||
uint sampleRate;
|
||||
std::string deviceSpecifier;
|
||||
};
|
||||
|
||||
/// @brief AL source adapter
|
||||
|
|
@ -90,7 +124,7 @@ namespace audio {
|
|||
float volume = 0.0f;
|
||||
public:
|
||||
ALStream* stream = nullptr;
|
||||
bool stopped = true;
|
||||
bool manuallyStopped = true;
|
||||
bool paused = false;
|
||||
uint source;
|
||||
duration_t duration = 0.0f;
|
||||
|
|
@ -130,6 +164,8 @@ namespace audio {
|
|||
bool isRelative() const override;
|
||||
|
||||
int getPriority() const override;
|
||||
|
||||
bool isManuallyStopped() const override;
|
||||
};
|
||||
|
||||
class ALAudio : public Backend {
|
||||
|
|
@ -152,15 +188,24 @@ namespace audio {
|
|||
void freeSource(uint source);
|
||||
void freeBuffer(uint buffer);
|
||||
|
||||
std::vector<std::string> getAvailableDevices() const;
|
||||
|
||||
std::unique_ptr<Sound> createSound(
|
||||
std::shared_ptr<PCM> pcm, bool keepPCM
|
||||
) override;
|
||||
|
||||
std::unique_ptr<Stream> openStream(
|
||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||
) override;
|
||||
|
||||
std::unique_ptr<InputDevice> openInputDevice(
|
||||
const std::string& deviceName,
|
||||
uint sampleRate,
|
||||
uint channels,
|
||||
uint bitsPerSample
|
||||
) override;
|
||||
|
||||
std::vector<std::string> getOutputDeviceNames() override;
|
||||
std::vector<std::string> getInputDeviceNames() override;
|
||||
|
||||
void setListener(
|
||||
glm::vec3 position,
|
||||
glm::vec3 velocity,
|
||||
|
|
|
|||
67
src/audio/MemoryPCMStream.cpp
Normal file
67
src/audio/MemoryPCMStream.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#include "MemoryPCMStream.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace audio;
|
||||
|
||||
MemoryPCMStream::MemoryPCMStream(
|
||||
uint sampleRate, uint channels, uint bitsPerSample
|
||||
)
|
||||
: sampleRate(sampleRate), channels(channels), bitsPerSample(bitsPerSample) {
|
||||
}
|
||||
|
||||
void MemoryPCMStream::feed(util::span<ubyte> bytes) {
|
||||
buffer.insert(buffer.end(), bytes.begin(), bytes.end());
|
||||
}
|
||||
|
||||
bool MemoryPCMStream::isOpen() const {
|
||||
return open;
|
||||
}
|
||||
|
||||
void MemoryPCMStream::close() {
|
||||
open = false;
|
||||
buffer = {};
|
||||
}
|
||||
|
||||
size_t MemoryPCMStream::read(char* dst, size_t bufferSize) {
|
||||
if (!open) {
|
||||
return PCMStream::ERROR;
|
||||
}
|
||||
if (buffer.empty()) {
|
||||
return 0;
|
||||
}
|
||||
size_t count = std::min<size_t>(bufferSize, buffer.size());
|
||||
std::memcpy(dst, buffer.data(), count);
|
||||
buffer.erase(buffer.begin(), buffer.begin() + count);
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t MemoryPCMStream::getTotalSamples() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
duration_t MemoryPCMStream::getTotalDuration() const {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
uint MemoryPCMStream::getChannels() const {
|
||||
return channels;
|
||||
}
|
||||
|
||||
uint MemoryPCMStream::getSampleRate() const {
|
||||
return sampleRate;
|
||||
}
|
||||
|
||||
uint MemoryPCMStream::getBitsPerSample() const {
|
||||
return bitsPerSample;
|
||||
}
|
||||
|
||||
bool MemoryPCMStream::isSeekable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void MemoryPCMStream::seek(size_t position) {}
|
||||
|
||||
size_t MemoryPCMStream::available() const {
|
||||
return buffer.size();
|
||||
}
|
||||
44
src/audio/MemoryPCMStream.hpp
Normal file
44
src/audio/MemoryPCMStream.hpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "util/span.hpp"
|
||||
|
||||
namespace audio {
|
||||
class MemoryPCMStream : public PCMStream {
|
||||
public:
|
||||
MemoryPCMStream(uint sampleRate, uint channels, uint bitsPerSample);
|
||||
|
||||
void feed(util::span<ubyte> bytes);
|
||||
|
||||
bool isOpen() const override;
|
||||
|
||||
void close() override;
|
||||
|
||||
size_t read(char* buffer, size_t bufferSize) override;
|
||||
|
||||
size_t getTotalSamples() const override;
|
||||
|
||||
duration_t getTotalDuration() const override;
|
||||
|
||||
uint getChannels() const override;
|
||||
|
||||
uint getSampleRate() const override;
|
||||
|
||||
uint getBitsPerSample() const override;
|
||||
|
||||
bool isSeekable() const override;
|
||||
|
||||
void seek(size_t position) override;
|
||||
|
||||
size_t available() const;
|
||||
private:
|
||||
uint sampleRate;
|
||||
uint channels;
|
||||
uint bitsPerSample;
|
||||
bool open = true;
|
||||
|
||||
std::vector<ubyte> buffer;
|
||||
};
|
||||
}
|
||||
|
|
@ -61,6 +61,13 @@ namespace audio {
|
|||
|
||||
void setTime(duration_t time) override {
|
||||
}
|
||||
|
||||
bool isStopOnEnd() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void setStopOnEnd(bool stopOnEnd) override {
|
||||
}
|
||||
};
|
||||
|
||||
class NoAudio : public Backend {
|
||||
|
|
@ -71,10 +78,24 @@ namespace audio {
|
|||
std::unique_ptr<Sound> createSound(
|
||||
std::shared_ptr<PCM> pcm, bool keepPCM
|
||||
) override;
|
||||
|
||||
std::unique_ptr<Stream> openStream(
|
||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||
) override;
|
||||
|
||||
std::unique_ptr<InputDevice> openInputDevice(
|
||||
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
|
||||
) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::string> getInputDeviceNames() override {
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> getOutputDeviceNames() override {
|
||||
return {};
|
||||
}
|
||||
|
||||
void setListener(
|
||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
||||
) override {
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
static std::unique_ptr<InputDevice> input_device = nullptr;
|
||||
|
||||
void audio::initialize(bool enabled, AudioSettings& settings) {
|
||||
enabled = enabled && settings.enabled.get();
|
||||
if (enabled) {
|
||||
|
|
@ -180,6 +182,15 @@ void audio::initialize(bool enabled, AudioSettings& settings) {
|
|||
audio::get_channel(channel.name)->setVolume(value * value);
|
||||
}, true));
|
||||
}
|
||||
|
||||
input_device = backend->openInputDevice("", 44100, 1, 16);
|
||||
if (input_device) {
|
||||
input_device->startCapture();
|
||||
}
|
||||
}
|
||||
|
||||
InputDevice* audio::get_input_device() {
|
||||
return input_device.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<PCM> audio::load_PCM(const io::path& file, bool headerOnly) {
|
||||
|
|
@ -242,6 +253,38 @@ std::unique_ptr<Stream> audio::open_stream(
|
|||
return backend->openStream(std::move(stream), keepSource);
|
||||
}
|
||||
|
||||
std::unique_ptr<InputDevice> audio::open_input_device(
|
||||
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
|
||||
) {
|
||||
return backend->openInputDevice(
|
||||
deviceName, sampleRate, channels, bitsPerSample
|
||||
);
|
||||
}
|
||||
|
||||
std::vector<std::string> audio::get_input_devices_names() {
|
||||
return backend->getInputDeviceNames();
|
||||
}
|
||||
|
||||
std::vector<std::string> audio::get_output_devices_names() {
|
||||
return backend->getOutputDeviceNames();
|
||||
}
|
||||
|
||||
void audio::set_input_device(const std::string& deviceName) {
|
||||
auto newDevice = backend->openInputDevice(deviceName, 44100, 1, 16);
|
||||
if (newDevice == nullptr) {
|
||||
logger.error() << "could not open input device: " << deviceName;
|
||||
return;
|
||||
}
|
||||
|
||||
if (input_device) {
|
||||
input_device->stopCapture();
|
||||
}
|
||||
input_device = std::move(newDevice);
|
||||
if (input_device) {
|
||||
input_device->startCapture();
|
||||
}
|
||||
}
|
||||
|
||||
void audio::set_listener(
|
||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up
|
||||
) {
|
||||
|
|
@ -421,8 +464,15 @@ void audio::update(double delta) {
|
|||
speaker->update(channel);
|
||||
}
|
||||
if (speaker->isStopped()) {
|
||||
streams.erase(it->first);
|
||||
it = speakers.erase(it);
|
||||
auto foundStream = streams.find(it->first);
|
||||
if (foundStream == streams.end() ||
|
||||
(!speaker->isManuallyStopped() &&
|
||||
foundStream->second->isStopOnEnd())) {
|
||||
streams.erase(it->first);
|
||||
it = speakers.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
|
|
@ -458,6 +508,9 @@ void audio::reset_channel(int index) {
|
|||
}
|
||||
|
||||
void audio::close() {
|
||||
if (input_device) {
|
||||
input_device->stopCapture();
|
||||
}
|
||||
speakers.clear();
|
||||
delete backend;
|
||||
backend = nullptr;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ namespace audio {
|
|||
/// @brief streams and important sounds
|
||||
constexpr inline int PRIORITY_HIGH = 10;
|
||||
|
||||
constexpr inline size_t MAX_INPUT_SAMPLES = 22050;
|
||||
|
||||
class Speaker;
|
||||
|
||||
/// @brief Audio speaker states
|
||||
|
|
@ -108,6 +110,31 @@ namespace audio {
|
|||
}
|
||||
};
|
||||
|
||||
class InputDevice {
|
||||
public:
|
||||
virtual ~InputDevice() {};
|
||||
|
||||
virtual void startCapture() = 0;
|
||||
virtual void stopCapture() = 0;
|
||||
|
||||
/// @brief Get number of audio channels
|
||||
/// @return 1 if mono, 2 if stereo
|
||||
virtual uint getChannels() const = 0;
|
||||
/// @brief Get audio sampling frequency
|
||||
/// @return number of mono samples per second
|
||||
virtual uint getSampleRate() const = 0;
|
||||
/// @brief Get number of bits per mono sample
|
||||
/// @return 8 or 16
|
||||
virtual uint getBitsPerSample() const = 0;
|
||||
|
||||
/// @brief Read available data to buffer.
|
||||
/// @return size of data received or PCMStream::ERROR in case of error
|
||||
virtual size_t read(char* buffer, size_t bufferSize) = 0;
|
||||
|
||||
/// @brief Get device specifier string
|
||||
virtual const std::string& getDeviceSpecifier() const = 0;
|
||||
};
|
||||
|
||||
/// @brief audio::PCMStream is a data source for audio::Stream
|
||||
class PCMStream {
|
||||
public:
|
||||
|
|
@ -121,6 +148,10 @@ namespace audio {
|
|||
/// (always equals bufferSize if seekable and looped)
|
||||
virtual size_t readFully(char* buffer, size_t bufferSize, bool loop);
|
||||
|
||||
/// @brief Read available data to buffer
|
||||
/// @param buffer destination buffer
|
||||
/// @param bufferSize destination buffer size
|
||||
/// @return count of received bytes or PCMStream::ERROR
|
||||
virtual size_t read(char* buffer, size_t bufferSize) = 0;
|
||||
|
||||
/// @brief Close stream
|
||||
|
|
@ -195,6 +226,9 @@ namespace audio {
|
|||
/// @brief Set playhead to the selected time
|
||||
/// @param time selected time
|
||||
virtual void setTime(duration_t time) = 0;
|
||||
|
||||
virtual bool isStopOnEnd() const = 0;
|
||||
virtual void setStopOnEnd(bool stopOnEnd) = 0;
|
||||
};
|
||||
|
||||
/// @brief Sound is an audio asset that supposed to support many
|
||||
|
|
@ -329,6 +363,8 @@ namespace audio {
|
|||
inline bool isStopped() const {
|
||||
return getState() == State::stopped;
|
||||
}
|
||||
|
||||
virtual bool isManuallyStopped() const = 0;
|
||||
};
|
||||
|
||||
class Backend {
|
||||
|
|
@ -341,12 +377,20 @@ namespace audio {
|
|||
virtual std::unique_ptr<Stream> openStream(
|
||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||
) = 0;
|
||||
virtual std::unique_ptr<InputDevice> openInputDevice(
|
||||
const std::string& deviceName,
|
||||
uint sampleRate,
|
||||
uint channels,
|
||||
uint bitsPerSample
|
||||
) = 0;
|
||||
virtual void setListener(
|
||||
glm::vec3 position,
|
||||
glm::vec3 velocity,
|
||||
glm::vec3 lookAt,
|
||||
glm::vec3 up
|
||||
) = 0;
|
||||
virtual std::vector<std::string> getInputDeviceNames() = 0;
|
||||
virtual std::vector<std::string> getOutputDeviceNames() = 0;
|
||||
virtual void update(double delta) = 0;
|
||||
|
||||
/// @brief Check if backend is an abstraction that does not internally
|
||||
|
|
@ -402,6 +446,28 @@ namespace audio {
|
|||
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||
);
|
||||
|
||||
/// @brief Open audio input device
|
||||
/// @param sampleRate sample rate
|
||||
/// @param channels channels count (1 - mono, 2 - stereo)
|
||||
/// @param bitsPerSample number of bits per sample (8 or 16)
|
||||
/// @return new InputDevice instance or nullptr
|
||||
std::unique_ptr<InputDevice> open_input_device(
|
||||
const std::string& deviceName,
|
||||
uint sampleRate,
|
||||
uint channels,
|
||||
uint bitsPerSample
|
||||
);
|
||||
|
||||
/// @brief Retrieve names of available audio input devices
|
||||
/// @return list of device names
|
||||
std::vector<std::string> get_input_devices_names();
|
||||
|
||||
/// @brief Retrieve names of available audio output devices
|
||||
/// @return list of device names
|
||||
std::vector<std::string> get_output_devices_names();
|
||||
|
||||
void set_input_device(const std::string& deviceName);
|
||||
|
||||
/// @brief Configure 3D listener
|
||||
/// @param position listener position
|
||||
/// @param velocity listener velocity (used for Doppler effect)
|
||||
|
|
@ -515,6 +581,8 @@ namespace audio {
|
|||
/// @brief Stop all playing audio in channel, reset channel state
|
||||
void reset_channel(int channel);
|
||||
|
||||
InputDevice* get_input_device();
|
||||
|
||||
/// @brief Finalize audio system
|
||||
void close();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
#include <utility>
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "coders/json.hpp"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#define VC_ENABLE_REFLECTION
|
||||
#include "imageio.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
|
@ -7,28 +8,34 @@
|
|||
#include "io/io.hpp"
|
||||
#include "png.hpp"
|
||||
|
||||
using namespace imageio;
|
||||
|
||||
using image_reader =
|
||||
std::function<std::unique_ptr<ImageData>(const ubyte*, size_t)>;
|
||||
using image_writer = std::function<void(const std::string&, const ImageData*)>;
|
||||
|
||||
static std::unordered_map<std::string, image_reader> readers {
|
||||
{".png", png::load_image},
|
||||
static std::unordered_map<ImageFileFormat, image_reader> readers {
|
||||
{ImageFileFormat::PNG, png::load_image},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, image_writer> writers {
|
||||
{".png", png::write_image},
|
||||
static std::unordered_map<ImageFileFormat, image_writer> writers {
|
||||
{ImageFileFormat::PNG, png::write_image},
|
||||
};
|
||||
|
||||
bool imageio::is_read_supported(const std::string& extension) {
|
||||
return readers.find(extension) != readers.end();
|
||||
return extension == ".png";
|
||||
}
|
||||
|
||||
bool imageio::is_write_supported(const std::string& extension) {
|
||||
return writers.find(extension) != writers.end();
|
||||
return extension == ".png";
|
||||
}
|
||||
|
||||
std::unique_ptr<ImageData> imageio::read(const io::path& file) {
|
||||
auto found = readers.find(file.extension());
|
||||
ImageFileFormat format;
|
||||
if (!ImageFileFormatMeta.getItem(file.extension().substr(1), format)) {
|
||||
throw std::runtime_error("unsupported image format");
|
||||
}
|
||||
auto found = readers.find(format);
|
||||
if (found == readers.end()) {
|
||||
throw std::runtime_error(
|
||||
"file format is not supported (read): " + file.string()
|
||||
|
|
@ -44,8 +51,25 @@ std::unique_ptr<ImageData> imageio::read(const io::path& file) {
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<ImageData> imageio::decode(
|
||||
ImageFileFormat format, util::span<ubyte> src
|
||||
) {
|
||||
auto found = readers.find(format);
|
||||
try {
|
||||
return std::unique_ptr<ImageData>(found->second(src.data(), src.size()));
|
||||
} catch (const std::runtime_error& err) {
|
||||
throw std::runtime_error(
|
||||
"could not to decode image: " + std::string(err.what())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void imageio::write(const io::path& file, const ImageData* image) {
|
||||
auto found = writers.find(file.extension());
|
||||
ImageFileFormat format;
|
||||
if (!ImageFileFormatMeta.getItem(file.extension().substr(1), format)) {
|
||||
throw std::runtime_error("unsupported image format");
|
||||
}
|
||||
auto found = writers.find(format);
|
||||
if (found == writers.end()) {
|
||||
throw std::runtime_error(
|
||||
"file format is not supported (write): " + file.string()
|
||||
|
|
@ -53,3 +77,14 @@ void imageio::write(const io::path& file, const ImageData* image) {
|
|||
}
|
||||
return found->second(io::resolve(file).u8string(), image);
|
||||
}
|
||||
|
||||
util::Buffer<unsigned char> imageio::encode(
|
||||
ImageFileFormat format, const ImageData& image
|
||||
) {
|
||||
switch (format) {
|
||||
case ImageFileFormat::PNG:
|
||||
return png::encode_image(image);
|
||||
default:
|
||||
throw std::runtime_error("file format is not supported for encoding");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,22 @@
|
|||
#include <string>
|
||||
|
||||
#include "io/fwd.hpp"
|
||||
#include "util/Buffer.hpp"
|
||||
#include "util/EnumMetadata.hpp"
|
||||
#include "util/span.hpp"
|
||||
#include "typedefs.hpp"
|
||||
|
||||
class ImageData;
|
||||
|
||||
namespace imageio {
|
||||
enum class ImageFileFormat {
|
||||
PNG
|
||||
};
|
||||
|
||||
VC_ENUM_METADATA(ImageFileFormat)
|
||||
{"png", ImageFileFormat::PNG},
|
||||
VC_ENUM_END
|
||||
|
||||
inline const std::string PNG = ".png";
|
||||
|
||||
bool is_read_supported(const std::string& extension);
|
||||
|
|
@ -15,4 +27,6 @@ namespace imageio {
|
|||
|
||||
std::unique_ptr<ImageData> read(const io::path& file);
|
||||
void write(const io::path& file, const ImageData* image);
|
||||
std::unique_ptr<ImageData> decode(ImageFileFormat format, util::span<ubyte> src);
|
||||
util::Buffer<unsigned char> encode(ImageFileFormat format, const ImageData& image);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,60 @@
|
|||
|
||||
static debug::Logger logger("png-coder");
|
||||
|
||||
static util::Buffer<ubyte> write_to_memory(uint width, uint height, const ubyte* data, bool alpha) {
|
||||
uint pixsize = alpha ? 4 : 3;
|
||||
|
||||
std::vector<ubyte> buffer;
|
||||
png_structp png_ptr = png_create_write_struct(
|
||||
PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr
|
||||
);
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
|
||||
png_set_write_fn(
|
||||
png_ptr,
|
||||
&buffer,
|
||||
[](png_structp pngPtr, png_bytep data, png_size_t length) {
|
||||
auto& buf = *reinterpret_cast<std::vector<ubyte>*>(png_get_io_ptr(pngPtr));
|
||||
buf.insert(
|
||||
buf.end(),
|
||||
reinterpret_cast<ubyte*>(data),
|
||||
reinterpret_cast<ubyte*>(data) + length
|
||||
);
|
||||
},
|
||||
nullptr
|
||||
);
|
||||
|
||||
png_set_IHDR(
|
||||
png_ptr,
|
||||
info_ptr,
|
||||
width,
|
||||
height,
|
||||
8,
|
||||
alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB,
|
||||
PNG_INTERLACE_NONE,
|
||||
PNG_COMPRESSION_TYPE_BASE,
|
||||
PNG_FILTER_TYPE_BASE
|
||||
);
|
||||
|
||||
png_write_info(png_ptr, info_ptr);
|
||||
|
||||
auto row = std::make_unique<png_byte[]>(pixsize * width);
|
||||
for (uint y = 0; y < height; y++) {
|
||||
for (uint x = 0; x < width; x++) {
|
||||
for (uint i = 0; i < pixsize; i++) {
|
||||
row[x * pixsize + i] =
|
||||
(png_byte)data[(y * width + x) * pixsize + i];
|
||||
}
|
||||
}
|
||||
png_write_row(png_ptr, row.get());
|
||||
}
|
||||
|
||||
png_write_end(png_ptr, nullptr);
|
||||
png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
return util::Buffer<ubyte>(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
// returns 0 if all-right, 1 otherwise
|
||||
static int png_write(
|
||||
const char* filename, uint width, uint height, const ubyte* data, bool alpha
|
||||
|
|
@ -230,3 +284,13 @@ void png::write_image(const std::string& filename, const ImageData* image) {
|
|||
image->getFormat() == ImageFormat::rgba8888
|
||||
);
|
||||
}
|
||||
|
||||
util::Buffer<ubyte> png::encode_image(const ImageData& image) {
|
||||
auto format = image.getFormat();
|
||||
return write_to_memory(
|
||||
image.getWidth(),
|
||||
image.getHeight(),
|
||||
image.getData(),
|
||||
format == ImageFormat::rgba8888
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <string>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "util/Buffer.hpp"
|
||||
|
||||
class Texture;
|
||||
class ImageData;
|
||||
|
|
@ -11,6 +12,7 @@ class ImageData;
|
|||
namespace png {
|
||||
std::unique_ptr<ImageData> load_image(const ubyte* bytes, size_t size);
|
||||
void write_image(const std::string& filename, const ImageData* image);
|
||||
util::Buffer<ubyte> encode_image(const ImageData& image);
|
||||
std::unique_ptr<Texture> load_texture(const ubyte* bytes, size_t size);
|
||||
std::unique_ptr<Texture> load_texture(const std::string& filename);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ inline constexpr int ITEM_ICON_SIZE = 48;
|
|||
|
||||
inline constexpr int TRANSLUCENT_BLOCKS_SORT_INTERVAL = 8;
|
||||
|
||||
inline constexpr int ATLAS_EXTRUSION = 2;
|
||||
|
||||
inline const std::string SHADERS_FOLDER = "shaders";
|
||||
inline const std::string TEXTURES_FOLDER = "textures";
|
||||
inline const std::string FONTS_FOLDER = "fonts";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include "ContentControl.hpp"
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "Content.hpp"
|
||||
#include "ContentPack.hpp"
|
||||
#include "ContentBuilder.hpp"
|
||||
|
|
@ -16,6 +16,13 @@ static void load_configs(Input& input, const io::path& root) {
|
|||
auto configFolder = root / "config";
|
||||
}
|
||||
|
||||
static std::vector<io::path> default_content_sources {
|
||||
"world:content",
|
||||
"user:content",
|
||||
"project:content",
|
||||
"res:content",
|
||||
};
|
||||
|
||||
ContentControl::ContentControl(
|
||||
const Project& project,
|
||||
EnginePaths& paths,
|
||||
|
|
@ -27,11 +34,7 @@ ContentControl::ContentControl(
|
|||
postContent(std::move(postContent)),
|
||||
basePacks(project.basePacks),
|
||||
manager(std::make_unique<PacksManager>()) {
|
||||
manager->setSources({
|
||||
"world:content",
|
||||
"user:content",
|
||||
"res:content",
|
||||
});
|
||||
manager->setSources(default_content_sources);
|
||||
}
|
||||
|
||||
ContentControl::~ContentControl() = default;
|
||||
|
|
@ -48,10 +51,10 @@ std::vector<std::string>& ContentControl::getBasePacks() {
|
|||
return basePacks;
|
||||
}
|
||||
|
||||
void ContentControl::resetContent() {
|
||||
void ContentControl::resetContent(const std::vector<std::string>& nonReset) {
|
||||
paths.setCurrentWorldFolder("");
|
||||
|
||||
scripting::cleanup();
|
||||
scripting::cleanup(nonReset);
|
||||
std::vector<PathsRoot> resRoots;
|
||||
{
|
||||
auto pack = ContentPack::createCore();
|
||||
|
|
@ -67,6 +70,7 @@ void ContentControl::resetContent() {
|
|||
scripting::on_content_reset();
|
||||
|
||||
setContentPacksRaw(manager->getAll(basePacks));
|
||||
resetContentSources();
|
||||
|
||||
postContent();
|
||||
}
|
||||
|
|
@ -78,8 +82,6 @@ void ContentControl::loadContent(const std::vector<std::string>& names) {
|
|||
}
|
||||
|
||||
void ContentControl::loadContent() {
|
||||
scripting::cleanup();
|
||||
|
||||
std::vector<std::string> names;
|
||||
for (auto& pack : contentPacks) {
|
||||
names.push_back(pack.id);
|
||||
|
|
@ -140,3 +142,15 @@ PacksManager& ContentControl::scan() {
|
|||
manager->scan();
|
||||
return *manager;
|
||||
}
|
||||
|
||||
void ContentControl::setContentSources(std::vector<io::path> sources) {
|
||||
manager->setSources(std::move(sources));
|
||||
}
|
||||
|
||||
void ContentControl::resetContentSources() {
|
||||
manager->setSources(default_content_sources);
|
||||
}
|
||||
|
||||
const std::vector<io::path>& ContentControl::getContentSources() const {
|
||||
return manager->getSources();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include "io/path.hpp"
|
||||
#include "ContentPack.hpp"
|
||||
|
||||
class Content;
|
||||
|
|
@ -13,10 +14,6 @@ class EnginePaths;
|
|||
class Input;
|
||||
struct Project;
|
||||
|
||||
namespace io {
|
||||
class path;
|
||||
}
|
||||
|
||||
class ContentControl {
|
||||
public:
|
||||
ContentControl(
|
||||
|
|
@ -34,7 +31,7 @@ public:
|
|||
std::vector<std::string>& getBasePacks();
|
||||
|
||||
/// @brief Reset content to base packs list
|
||||
void resetContent();
|
||||
void resetContent(const std::vector<std::string>& nonReset);
|
||||
|
||||
void loadContent(const std::vector<std::string>& names);
|
||||
|
||||
|
|
@ -46,6 +43,10 @@ public:
|
|||
const std::vector<ContentPack>& getAllContentPacks() const;
|
||||
|
||||
PacksManager& scan();
|
||||
|
||||
void setContentSources(std::vector<io::path> sources);
|
||||
void resetContentSources();
|
||||
const std::vector<io::path>& getContentSources() const;
|
||||
private:
|
||||
EnginePaths& paths;
|
||||
Input& input;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
#include "objects/rigging.hpp"
|
||||
#include "util/listutil.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
|
||||
static debug::Logger logger("content-loader");
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#include "coders/json.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "data/dv.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "coders/commons.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@
|
|||
|
||||
PacksManager::PacksManager() = default;
|
||||
|
||||
const std::vector<io::path>& PacksManager::getSources() const {
|
||||
return sources;
|
||||
}
|
||||
|
||||
void PacksManager::setSources(std::vector<io::path> sources) {
|
||||
this->sources = std::move(sources);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ class PacksManager {
|
|||
public:
|
||||
PacksManager();
|
||||
|
||||
/// @brief Get current content packs sources
|
||||
const std::vector<io::path>& getSources() const;
|
||||
|
||||
/// @brief Set content packs sources (search folders)
|
||||
void setSources(std::vector<io::path> sources);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
#include "../ContentPack.hpp"
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "world/generator/GeneratorDef.hpp"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#include "content/Content.hpp"
|
||||
#include "content/ContentBuilder.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "window/input.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
#include "coders/toml.hpp"
|
||||
|
|
|
|||
|
|
@ -197,6 +197,7 @@ bool DebuggingServer::performCommand(
|
|||
connectionEstablished = true;
|
||||
logger.info() << "client connection established";
|
||||
connection->sendResponse("success");
|
||||
return true;
|
||||
}
|
||||
if (!connectionEstablished) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include "Editor.hpp"
|
||||
|
||||
#include "engine/Engine.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "coders/syntax_parser.hpp"
|
||||
#include "SyntaxProcessor.hpp"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
#include "Project.hpp"
|
||||
|
||||
#include "data/dv_util.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "io/path.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
|
||||
static debug::Logger logger("project");
|
||||
|
||||
Project::~Project() = default;
|
||||
|
||||
dv::value Project::serialize() const {
|
||||
|
|
@ -18,3 +23,23 @@ void Project::deserialize(const dv::value& src) {
|
|||
src.at("title").get(title);
|
||||
dv::get(src, "base_packs", basePacks);
|
||||
}
|
||||
|
||||
void Project::loadProjectClientScript() {
|
||||
io::path scriptFile = "project:project_client.lua";
|
||||
if (io::exists(scriptFile)) {
|
||||
logger.info() << "starting project client script";
|
||||
clientScript = scripting::load_client_project_script(scriptFile);
|
||||
} else {
|
||||
logger.warning() << "project client script does not exists";
|
||||
}
|
||||
}
|
||||
|
||||
void Project::loadProjectStartScript() {
|
||||
io::path scriptFile = "project:start.lua";
|
||||
if (io::exists(scriptFile)) {
|
||||
logger.info() << "starting project start script";
|
||||
setupCoroutine = scripting::start_app_script(scriptFile);
|
||||
} else {
|
||||
logger.warning() << "project start script does not exists";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "interfaces/Process.hpp"
|
||||
#include "interfaces/Serializable.hpp"
|
||||
|
||||
namespace scripting {
|
||||
|
|
@ -15,9 +16,13 @@ struct Project : Serializable {
|
|||
std::string title;
|
||||
std::vector<std::string> basePacks;
|
||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||
std::unique_ptr<Process> setupCoroutine;
|
||||
|
||||
~Project();
|
||||
|
||||
dv::value serialize() const override;
|
||||
void deserialize(const dv::value& src) override;
|
||||
|
||||
void loadProjectClientScript();
|
||||
void loadProjectStartScript();
|
||||
};
|
||||
|
|
|
|||
15
src/engine/CoreParameters.hpp
Normal file
15
src/engine/CoreParameters.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
struct CoreParameters {
|
||||
bool headless = false;
|
||||
bool testMode = false;
|
||||
std::filesystem::path resFolder = "res";
|
||||
std::filesystem::path userFolder = ".";
|
||||
std::filesystem::path scriptFile;
|
||||
std::filesystem::path projectFolder;
|
||||
std::string debugServerString;
|
||||
int tps = 20;
|
||||
};
|
||||
|
|
@ -8,8 +8,6 @@
|
|||
#include "assets/AssetsLoader.hpp"
|
||||
#include "audio/audio.hpp"
|
||||
#include "coders/GLSLExtension.hpp"
|
||||
#include "coders/imageio.hpp"
|
||||
#include "coders/json.hpp"
|
||||
#include "coders/toml.hpp"
|
||||
#include "coders/commons.hpp"
|
||||
#include "devtools/Editor.hpp"
|
||||
|
|
@ -18,27 +16,28 @@
|
|||
#include "content/ContentControl.hpp"
|
||||
#include "core_defs.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "io/settings_io.hpp"
|
||||
#include "frontend/locale.hpp"
|
||||
#include "frontend/menu.hpp"
|
||||
#include "frontend/screens/Screen.hpp"
|
||||
#include "graphics/render/ModelsGenerator.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/ImageData.hpp"
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "objects/rigging.hpp"
|
||||
#include "graphics/ui/elements/Menu.hpp"
|
||||
#include "logic/EngineController.hpp"
|
||||
#include "logic/CommandsInterpreter.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "logic/scripting/scripting_hud.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "util/platform.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "window/input.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "Mainloop.hpp"
|
||||
#include "ServerMainloop.hpp"
|
||||
#include "WindowControl.hpp"
|
||||
#include "EnginePaths.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
|
|
@ -49,29 +48,6 @@
|
|||
|
||||
static debug::Logger logger("engine");
|
||||
|
||||
static std::unique_ptr<ImageData> load_icon() {
|
||||
try {
|
||||
auto file = "res:textures/misc/icon.png";
|
||||
if (io::exists(file)) {
|
||||
return imageio::read(file);
|
||||
}
|
||||
} catch (const std::exception& err) {
|
||||
logger.error() << "could not load window icon: " << err.what();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::unique_ptr<scripting::IClientProjectScript> load_client_project_script() {
|
||||
io::path scriptFile = "project:project_client.lua";
|
||||
if (io::exists(scriptFile)) {
|
||||
logger.info() << "starting project script";
|
||||
return scripting::load_client_project_script(scriptFile);
|
||||
} else {
|
||||
logger.warning() << "project script does not exists";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Engine::Engine() = default;
|
||||
Engine::~Engine() = default;
|
||||
|
||||
|
|
@ -86,7 +62,7 @@ Engine& Engine::getInstance() {
|
|||
|
||||
void Engine::onContentLoad() {
|
||||
editor->loadTools();
|
||||
langs::setup(langs::get_current(), paths.resPaths.collectRoots());
|
||||
langs::setup(langs::get_current(), paths->resPaths.collectRoots());
|
||||
|
||||
if (isHeadless()) {
|
||||
return;
|
||||
|
|
@ -94,6 +70,7 @@ void Engine::onContentLoad() {
|
|||
for (auto& pack : content->getAllContentPacks()) {
|
||||
auto configFolder = pack.folder / "config";
|
||||
auto bindsFile = configFolder / "bindings.toml";
|
||||
logger.info() << "loading bindings: " << bindsFile.string();
|
||||
if (io::is_regular_file(bindsFile)) {
|
||||
input->getBindings().read(
|
||||
toml::parse(
|
||||
|
|
@ -107,29 +84,9 @@ void Engine::onContentLoad() {
|
|||
}
|
||||
|
||||
void Engine::initializeClient() {
|
||||
std::string title = project->title;
|
||||
if (title.empty()) {
|
||||
title = "VoxelCore v" +
|
||||
std::to_string(ENGINE_VERSION_MAJOR) + "." +
|
||||
std::to_string(ENGINE_VERSION_MINOR);
|
||||
}
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
title += " [debug]";
|
||||
}
|
||||
if (debuggingServer) {
|
||||
title = "[debugging] " + title;
|
||||
}
|
||||
auto [window, input] = Window::initialize(&settings.display, title);
|
||||
if (!window || !input){
|
||||
throw initialize_error("could not initialize window");
|
||||
}
|
||||
window->setFramerate(settings.display.framerate.get());
|
||||
windowControl = std::make_unique<WindowControl>(*this);
|
||||
auto [window, input] = windowControl->initialize();
|
||||
|
||||
time.set(window->time());
|
||||
if (auto icon = load_icon()) {
|
||||
icon->flipY();
|
||||
window->setIcon(icon.get());
|
||||
}
|
||||
this->window = std::move(window);
|
||||
this->input = std::move(input);
|
||||
|
||||
|
|
@ -154,6 +111,14 @@ void Engine::initializeClient() {
|
|||
},
|
||||
true
|
||||
));
|
||||
|
||||
keepAlive(this->input->addKeyCallback(Keycode::ESCAPE, [this]() {
|
||||
auto& menu = *gui->getMenu();
|
||||
if (menu.hasOpenPage() && menu.back()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
}
|
||||
|
||||
void Engine::initialize(CoreParameters coreParameters) {
|
||||
|
|
@ -162,15 +127,12 @@ void Engine::initialize(CoreParameters coreParameters) {
|
|||
|
||||
logger.info() << "engine version: " << ENGINE_VERSION_STRING;
|
||||
if (params.headless) {
|
||||
logger.info() << "headless mode is enabled";
|
||||
logger.info() << "engine runs in headless mode";
|
||||
}
|
||||
if (params.projectFolder.empty()) {
|
||||
params.projectFolder = params.resFolder;
|
||||
}
|
||||
paths.setResourcesFolder(params.resFolder);
|
||||
paths.setUserFilesFolder(params.userFolder);
|
||||
paths.setProjectFolder(params.projectFolder);
|
||||
paths.prepare();
|
||||
paths = std::make_unique<EnginePaths>(params);
|
||||
loadProject();
|
||||
|
||||
editor = std::make_unique<devtools::Editor>(*this);
|
||||
|
|
@ -188,10 +150,6 @@ void Engine::initialize(CoreParameters coreParameters) {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.scriptFile.empty()) {
|
||||
paths.setScriptFolder(params.scriptFile.parent_path());
|
||||
}
|
||||
loadSettings();
|
||||
|
||||
controller = std::make_unique<EngineController>(*this);
|
||||
|
|
@ -205,7 +163,7 @@ void Engine::initialize(CoreParameters coreParameters) {
|
|||
langs::locale_by_envlocale(platform::detect_locale())
|
||||
);
|
||||
}
|
||||
content = std::make_unique<ContentControl>(*project, paths, *input, [this]() {
|
||||
content = std::make_unique<ContentControl>(*project, *paths, *input, [this]() {
|
||||
onContentLoad();
|
||||
});
|
||||
scripting::initialize(this);
|
||||
|
|
@ -214,10 +172,13 @@ void Engine::initialize(CoreParameters coreParameters) {
|
|||
gui->setPageLoader(scripting::create_page_loader());
|
||||
}
|
||||
keepAlive(settings.ui.language.observe([this](auto lang) {
|
||||
langs::setup(lang, paths.resPaths.collectRoots());
|
||||
langs::setup(lang, paths->resPaths.collectRoots());
|
||||
}, true));
|
||||
|
||||
project->clientScript = load_client_project_script();
|
||||
project->loadProjectStartScript();
|
||||
if (!params.headless) {
|
||||
project->loadProjectClientScript();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::loadSettings() {
|
||||
|
|
@ -247,29 +208,17 @@ void Engine::loadControls() {
|
|||
|
||||
void Engine::updateHotkeys() {
|
||||
if (input->jpressed(Keycode::F2)) {
|
||||
saveScreenshot();
|
||||
windowControl->saveScreenshot();
|
||||
}
|
||||
if (input->pressed(Keycode::LEFT_CONTROL) && input->pressed(Keycode::F3) &&
|
||||
input->jpressed(Keycode::U)) {
|
||||
gui->toggleDebug();
|
||||
}
|
||||
if (input->jpressed(Keycode::F11)) {
|
||||
if (settings.display.windowMode.get() != static_cast<int>(WindowMode::FULLSCREEN)) {
|
||||
settings.display.windowMode.set(static_cast<int>(WindowMode::FULLSCREEN));
|
||||
} else {
|
||||
settings.display.windowMode.set(static_cast<int>(WindowMode::WINDOWED));
|
||||
}
|
||||
windowControl->toggleFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::saveScreenshot() {
|
||||
auto image = window->takeScreenshot();
|
||||
image->flipY();
|
||||
io::path filename = paths.getNewScreenshotFile("png");
|
||||
imageio::write(filename.string(), image.get());
|
||||
logger.info() << "saved screenshot as " << filename.string();
|
||||
}
|
||||
|
||||
void Engine::run() {
|
||||
if (params.headless) {
|
||||
ServerMainloop(*this).run();
|
||||
|
|
@ -292,6 +241,12 @@ void Engine::detachDebugger() {
|
|||
debuggingServer.reset();
|
||||
}
|
||||
|
||||
void Engine::applicationTick() {
|
||||
if (project->setupCoroutine && project->setupCoroutine->isActive()) {
|
||||
project->setupCoroutine->update();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::updateFrontend() {
|
||||
double delta = time.getDelta();
|
||||
updateHotkeys();
|
||||
|
|
@ -301,15 +256,8 @@ void Engine::updateFrontend() {
|
|||
gui->postAct();
|
||||
}
|
||||
|
||||
void Engine::nextFrame() {
|
||||
window->setFramerate(
|
||||
window->isIconified() && settings.display.limitFpsIconified.get()
|
||||
? 20
|
||||
: settings.display.framerate.get()
|
||||
);
|
||||
window->swapBuffers();
|
||||
// todo: process it on another thread
|
||||
input->pollEvents();
|
||||
void Engine::nextFrame(bool waitForRefresh) {
|
||||
windowControl->nextFrame(waitForRefresh);
|
||||
}
|
||||
|
||||
void Engine::startPauseLoop() {
|
||||
|
|
@ -328,7 +276,7 @@ void Engine::startPauseLoop() {
|
|||
if (isHeadless()) {
|
||||
platform::sleep(1.0 / params.tps * 1000);
|
||||
} else {
|
||||
nextFrame();
|
||||
nextFrame(false);
|
||||
}
|
||||
}
|
||||
if (initialCursorLocked) {
|
||||
|
|
@ -399,17 +347,18 @@ void Engine::setLevelConsumer(OnWorldOpen levelConsumer) {
|
|||
|
||||
void Engine::loadAssets() {
|
||||
logger.info() << "loading assets";
|
||||
Shader::preprocessor->setPaths(&paths.resPaths);
|
||||
Shader::preprocessor->setPaths(&paths->resPaths);
|
||||
|
||||
auto content = this->content->get();
|
||||
|
||||
auto new_assets = std::make_unique<Assets>();
|
||||
AssetsLoader loader(*this, *new_assets, paths.resPaths);
|
||||
AssetsLoader loader(*this, *new_assets, paths->resPaths);
|
||||
AssetsLoader::addDefaults(loader, content);
|
||||
|
||||
// no need
|
||||
// correct log messages order is more useful
|
||||
bool threading = false; // look at two upper lines
|
||||
// todo: before setting to true, check if GLSLExtension thread safe
|
||||
bool threading = false; // look at three upper lines
|
||||
if (threading) {
|
||||
auto task = loader.startTask([=](){});
|
||||
task->waitForEnd();
|
||||
|
|
@ -446,6 +395,7 @@ void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
|||
}
|
||||
if (project->clientScript && this->screen) {
|
||||
project->clientScript->onScreenChange(this->screen->getName(), true);
|
||||
window->setShouldRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -479,11 +429,11 @@ Assets* Engine::getAssets() {
|
|||
}
|
||||
|
||||
EnginePaths& Engine::getPaths() {
|
||||
return paths;
|
||||
return *paths;
|
||||
}
|
||||
|
||||
ResPaths& Engine::getResPaths() {
|
||||
return paths.resPaths;
|
||||
return paths->resPaths;
|
||||
}
|
||||
|
||||
std::shared_ptr<Screen> Engine::getScreen() {
|
||||
|
|
|
|||
|
|
@ -1,25 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include "delegates.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "io/settings_io.hpp"
|
||||
#include "util/ObjectsKeeper.hpp"
|
||||
#include "CoreParameters.hpp"
|
||||
#include "PostRunnables.hpp"
|
||||
#include "Time.hpp"
|
||||
#include "delegates.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "util/ObjectsKeeper.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Window;
|
||||
class Assets;
|
||||
class Level;
|
||||
class Screen;
|
||||
class ContentControl;
|
||||
class EngineController;
|
||||
class EnginePaths;
|
||||
class Input;
|
||||
class Level;
|
||||
class ResPaths;
|
||||
class Screen;
|
||||
class SettingsHandler;
|
||||
class Window;
|
||||
class WindowControl;
|
||||
struct Project;
|
||||
|
||||
namespace gui {
|
||||
|
|
@ -44,24 +46,12 @@ public:
|
|||
initialize_error(const std::string& message) : std::runtime_error(message) {}
|
||||
};
|
||||
|
||||
struct CoreParameters {
|
||||
bool headless = false;
|
||||
bool testMode = false;
|
||||
std::filesystem::path resFolder = "res";
|
||||
std::filesystem::path userFolder = ".";
|
||||
std::filesystem::path scriptFile;
|
||||
std::filesystem::path projectFolder;
|
||||
std::string debugServerString;
|
||||
int tps = 20;
|
||||
};
|
||||
|
||||
using OnWorldOpen = std::function<void(std::unique_ptr<Level>, int64_t)>;
|
||||
|
||||
class Engine : public util::ObjectsKeeper {
|
||||
CoreParameters params;
|
||||
EngineSettings settings;
|
||||
EnginePaths paths;
|
||||
|
||||
std::unique_ptr<EnginePaths> paths;
|
||||
std::unique_ptr<Project> project;
|
||||
std::unique_ptr<SettingsHandler> settingsHandler;
|
||||
std::unique_ptr<Assets> assets;
|
||||
|
|
@ -75,6 +65,7 @@ class Engine : public util::ObjectsKeeper {
|
|||
std::unique_ptr<gui::GUI> gui;
|
||||
std::unique_ptr<devtools::Editor> editor;
|
||||
std::unique_ptr<devtools::DebuggingServer> debuggingServer;
|
||||
std::unique_ptr<WindowControl> windowControl;
|
||||
PostRunnables postRunnables;
|
||||
Time time;
|
||||
OnWorldOpen levelConsumer;
|
||||
|
|
@ -105,9 +96,10 @@ public:
|
|||
|
||||
void postUpdate();
|
||||
|
||||
void applicationTick();
|
||||
void updateFrontend();
|
||||
void renderFrame();
|
||||
void nextFrame();
|
||||
void nextFrame(bool waitForRefresh);
|
||||
void startPauseLoop();
|
||||
|
||||
/// @brief Set screen (scene).
|
||||
|
|
@ -143,8 +135,6 @@ public:
|
|||
postRunnables.postRunnable(callback);
|
||||
}
|
||||
|
||||
void saveScreenshot();
|
||||
|
||||
EngineController* getController();
|
||||
|
||||
void setLevelConsumer(OnWorldOpen levelConsumer);
|
||||
|
|
|
|||
|
|
@ -1,44 +1,57 @@
|
|||
#include "engine_paths.hpp"
|
||||
#include "EnginePaths.hpp"
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
#include "io/devices/StdfsDevice.hpp"
|
||||
#include "io/devices/MemoryDevice.hpp"
|
||||
#include "io/devices/ZipFileDevice.hpp"
|
||||
#include "maths/util.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "util/platform.hpp"
|
||||
#include "util/random.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "world/files/WorldFiles.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
#include "typedefs.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "util/platform.hpp"
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "io/devices/StdfsDevice.hpp"
|
||||
#include "io/devices/ZipFileDevice.hpp"
|
||||
#include "world/files/WorldFiles.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include "maths/util.hpp"
|
||||
|
||||
template<int n>
|
||||
static std::string generate_random_base64() {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto seed = now.time_since_epoch().count();
|
||||
|
||||
util::PseudoRandom random(seed); // fixme: replace with safe random
|
||||
ubyte bytes[n];
|
||||
random.rand(bytes, n);
|
||||
return util::base64_urlsafe_encode(bytes, n);
|
||||
}
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static debug::Logger logger("engine-paths");
|
||||
static std::random_device random_device;
|
||||
|
||||
static inline io::path SCREENSHOTS_FOLDER = "user:screenshots";
|
||||
static inline io::path CONTENT_FOLDER = "user:content";
|
||||
static inline io::path WORLDS_FOLDER = "user:worlds";
|
||||
|
||||
void EnginePaths::prepare() {
|
||||
static debug::Logger logger("engine-paths");
|
||||
|
||||
template<int n>
|
||||
static std::string generate_random_base64() {
|
||||
auto randomEngine = util::seeded_random_engine(random_device);
|
||||
static std::uniform_int_distribution<integer_t> dist(0, 0xFF);
|
||||
ubyte bytes[n];
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
bytes[i] = dist(randomEngine);
|
||||
}
|
||||
return util::base64_urlsafe_encode(bytes, n);
|
||||
}
|
||||
|
||||
EnginePaths::EnginePaths(CoreParameters& params)
|
||||
: resourcesFolder(params.resFolder),
|
||||
userFilesFolder(params.userFolder),
|
||||
projectFolder(params.projectFolder) {
|
||||
if (!params.scriptFile.empty()) {
|
||||
scriptFolder = params.scriptFile.parent_path();
|
||||
io::set_device("script", std::make_shared<io::StdfsDevice>(*scriptFolder));
|
||||
}
|
||||
|
||||
io::set_device("res", std::make_shared<io::StdfsDevice>(resourcesFolder, false));
|
||||
io::set_device("user", std::make_shared<io::StdfsDevice>(userFilesFolder));
|
||||
io::set_device("project", std::make_shared<io::StdfsDevice>(projectFolder));
|
||||
|
||||
if (!io::is_directory("res:")) {
|
||||
throw std::runtime_error(
|
||||
|
|
@ -59,15 +72,15 @@ void EnginePaths::prepare() {
|
|||
io::create_subdevice("config", "user", "config");
|
||||
}
|
||||
|
||||
const std::filesystem::path& EnginePaths::getUserFilesFolder() const {
|
||||
return userFilesFolder;
|
||||
}
|
||||
|
||||
const std::filesystem::path& EnginePaths::getResourcesFolder() const {
|
||||
std::filesystem::path EnginePaths::getResourcesFolder() const {
|
||||
return resourcesFolder;
|
||||
}
|
||||
|
||||
io::path EnginePaths::getNewScreenshotFile(const std::string& ext) {
|
||||
std::filesystem::path EnginePaths::getUserFilesFolder() const {
|
||||
return userFilesFolder;
|
||||
}
|
||||
|
||||
io::path EnginePaths::getNewScreenshotFile(const std::string& ext) const {
|
||||
auto folder = SCREENSHOTS_FOLDER;
|
||||
if (!io::is_directory(folder)) {
|
||||
io::create_directories(folder);
|
||||
|
|
@ -95,10 +108,6 @@ io::path EnginePaths::getWorldsFolder() const {
|
|||
return WORLDS_FOLDER;
|
||||
}
|
||||
|
||||
io::path EnginePaths::getCurrentWorldFolder() {
|
||||
return currentWorldFolder;
|
||||
}
|
||||
|
||||
io::path EnginePaths::getWorldFolderByName(const std::string& name) {
|
||||
return getWorldsFolder() / name;
|
||||
}
|
||||
|
|
@ -132,24 +141,6 @@ std::vector<io::path> EnginePaths::scanForWorlds() const {
|
|||
return folders;
|
||||
}
|
||||
|
||||
void EnginePaths::setUserFilesFolder(std::filesystem::path folder) {
|
||||
this->userFilesFolder = std::move(folder);
|
||||
}
|
||||
|
||||
void EnginePaths::setResourcesFolder(std::filesystem::path folder) {
|
||||
this->resourcesFolder = std::move(folder);
|
||||
}
|
||||
|
||||
void EnginePaths::setScriptFolder(std::filesystem::path folder) {
|
||||
io::set_device("script", std::make_shared<io::StdfsDevice>(folder));
|
||||
this->scriptFolder = std::move(folder);
|
||||
}
|
||||
|
||||
void EnginePaths::setProjectFolder(std::filesystem::path folder) {
|
||||
io::set_device("project", std::make_shared<io::StdfsDevice>(folder));
|
||||
this->projectFolder = std::move(folder);
|
||||
}
|
||||
|
||||
void EnginePaths::setCurrentWorldFolder(io::path folder) {
|
||||
if (folder.empty()) {
|
||||
io::remove_device("world");
|
||||
|
|
@ -186,6 +177,18 @@ void EnginePaths::unmount(const std::string& name) {
|
|||
mounted.erase(found);
|
||||
}
|
||||
|
||||
std::string EnginePaths::createMemoryDevice() {
|
||||
auto device = std::make_unique<io::MemoryDevice>();
|
||||
std::string name;
|
||||
do {
|
||||
name = std::string("W.") + generate_random_base64<6>();
|
||||
} while (std::find(mounted.begin(), mounted.end(), name) != mounted.end());
|
||||
|
||||
io::set_device(name, std::move(device));
|
||||
mounted.push_back(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string EnginePaths::createWriteableDevice(const std::string& name) {
|
||||
const auto& found = writeables.find(name);
|
||||
if (found != writeables.end()) {
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "data/dv.hpp"
|
||||
#include "CoreParameters.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include "io.hpp"
|
||||
#include "data/dv.hpp"
|
||||
|
||||
struct PathsRoot {
|
||||
std::string name;
|
||||
io::path path;
|
||||
|
|
@ -46,29 +46,22 @@ class EnginePaths {
|
|||
public:
|
||||
ResPaths resPaths;
|
||||
|
||||
void prepare();
|
||||
EnginePaths(CoreParameters& params);
|
||||
|
||||
void setUserFilesFolder(std::filesystem::path folder);
|
||||
const std::filesystem::path& getUserFilesFolder() const;
|
||||
|
||||
void setResourcesFolder(std::filesystem::path folder);
|
||||
const std::filesystem::path& getResourcesFolder() const;
|
||||
|
||||
void setScriptFolder(std::filesystem::path folder);
|
||||
|
||||
void setProjectFolder(std::filesystem::path folder);
|
||||
std::filesystem::path getResourcesFolder() const;
|
||||
std::filesystem::path getUserFilesFolder() const;
|
||||
|
||||
io::path getWorldFolderByName(const std::string& name);
|
||||
io::path getWorldsFolder() const;
|
||||
|
||||
void setCurrentWorldFolder(io::path folder);
|
||||
io::path getCurrentWorldFolder();
|
||||
io::path getNewScreenshotFile(const std::string& ext);
|
||||
io::path getNewScreenshotFile(const std::string& ext) const;
|
||||
|
||||
std::string mount(const io::path& file);
|
||||
void unmount(const std::string& name);
|
||||
|
||||
std::string createWriteableDevice(const std::string& name);
|
||||
std::string createMemoryDevice();
|
||||
|
||||
void setEntryPoints(std::vector<PathsRoot> entryPoints);
|
||||
|
||||
|
|
@ -80,9 +73,9 @@ public:
|
|||
static inline io::path CONTROLS_FILE = "user:controls.toml";
|
||||
static inline io::path SETTINGS_FILE = "user:settings.toml";
|
||||
private:
|
||||
std::filesystem::path userFilesFolder {"."};
|
||||
std::filesystem::path resourcesFolder {"res"};
|
||||
std::filesystem::path projectFolder = resourcesFolder;
|
||||
std::filesystem::path resourcesFolder;
|
||||
std::filesystem::path userFilesFolder;
|
||||
std::filesystem::path projectFolder;
|
||||
io::path currentWorldFolder;
|
||||
std::optional<std::filesystem::path> scriptFolder;
|
||||
std::vector<PathsRoot> entryPoints;
|
||||
|
|
@ -18,6 +18,7 @@ Mainloop::Mainloop(Engine& engine) : engine(engine) {
|
|||
void Mainloop::run() {
|
||||
auto& time = engine.getTime();
|
||||
auto& window = engine.getWindow();
|
||||
auto& settings = engine.getSettings();
|
||||
|
||||
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
|
||||
if (level == nullptr) {
|
||||
|
|
@ -38,13 +39,17 @@ void Mainloop::run() {
|
|||
logger.info() << "main loop started";
|
||||
while (!window.isShouldClose()){
|
||||
time.update(window.time());
|
||||
engine.applicationTick();
|
||||
engine.updateFrontend();
|
||||
|
||||
if (!window.isIconified()) {
|
||||
engine.renderFrame();
|
||||
}
|
||||
engine.postUpdate();
|
||||
engine.nextFrame();
|
||||
engine.nextFrame(
|
||||
settings.display.adaptiveFpsInMenu.get() &&
|
||||
dynamic_cast<const MenuScreen*>(engine.getScreen().get()) != nullptr
|
||||
);
|
||||
}
|
||||
logger.info() << "main loop stopped";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "ServerMainloop.hpp"
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "EnginePaths.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "interfaces/Process.hpp"
|
||||
|
|
@ -32,8 +33,7 @@ void ServerMainloop::run() {
|
|||
setLevel(std::move(level));
|
||||
});
|
||||
|
||||
logger.info() << "starting test " << coreParams.scriptFile.string();
|
||||
auto process = scripting::start_coroutine(
|
||||
auto process = scripting::start_app_script(
|
||||
"script:" + coreParams.scriptFile.filename().u8string()
|
||||
);
|
||||
|
||||
|
|
@ -61,6 +61,7 @@ void ServerMainloop::run() {
|
|||
controller->getLevel()->getWorld()->updateTimers(delta);
|
||||
controller->update(glm::min(delta, 0.2), false);
|
||||
}
|
||||
engine.applicationTick();
|
||||
engine.postUpdate();
|
||||
|
||||
if (!coreParams.testMode) {
|
||||
|
|
|
|||
94
src/engine/WindowControl.cpp
Normal file
94
src/engine/WindowControl.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#include "WindowControl.hpp"
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "devtools/Project.hpp"
|
||||
#include "coders/imageio.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "window/input.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "graphics/core/ImageData.hpp"
|
||||
#include "util/platform.hpp"
|
||||
|
||||
static debug::Logger logger("window-control");
|
||||
|
||||
namespace {
|
||||
static std::unique_ptr<ImageData> load_icon() {
|
||||
try {
|
||||
auto file = "res:textures/misc/icon.png";
|
||||
if (io::exists(file)) {
|
||||
return imageio::read(file);
|
||||
}
|
||||
} catch (const std::exception& err) {
|
||||
logger.error() << "could not load window icon: " << err.what();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
WindowControl::WindowControl(Engine& engine) : engine(engine) {}
|
||||
|
||||
WindowControl::Result WindowControl::initialize() {
|
||||
const auto& project = engine.getProject();
|
||||
auto& settings = engine.getSettings();
|
||||
|
||||
std::string title = project.title;
|
||||
if (!title.empty()) {
|
||||
title += " - ";
|
||||
}
|
||||
title += "VoxelCore v" +
|
||||
std::to_string(ENGINE_VERSION_MAJOR) + "." +
|
||||
std::to_string(ENGINE_VERSION_MINOR);
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
title += " [debug]";
|
||||
}
|
||||
if (engine.getDebuggingServer()) {
|
||||
title = "[debugging] " + title;
|
||||
}
|
||||
|
||||
auto [window, input] = Window::initialize(&settings.display, title);
|
||||
if (!window || !input){
|
||||
throw initialize_error("could not initialize window");
|
||||
}
|
||||
window->setFramerate(settings.display.framerate.get());
|
||||
if (auto icon = load_icon()) {
|
||||
icon->flipY();
|
||||
window->setIcon(icon.get());
|
||||
}
|
||||
|
||||
return Result {std::move(window), std::move(input)};
|
||||
}
|
||||
|
||||
void WindowControl::saveScreenshot() {
|
||||
auto& window = engine.getWindow();
|
||||
const auto& paths = engine.getPaths();
|
||||
|
||||
auto image = window.takeScreenshot();
|
||||
image->flipY();
|
||||
io::path filename = paths.getNewScreenshotFile("png");
|
||||
imageio::write(filename.string(), image.get());
|
||||
logger.info() << "saved screenshot as " << filename.string();
|
||||
}
|
||||
|
||||
void WindowControl::toggleFullscreen() {
|
||||
auto& settings = engine.getSettings();
|
||||
auto& windowMode = settings.display.windowMode;
|
||||
if (windowMode.get() != static_cast<int>(WindowMode::FULLSCREEN)) {
|
||||
windowMode.set(static_cast<int>(WindowMode::FULLSCREEN));
|
||||
} else {
|
||||
windowMode.set(static_cast<int>(WindowMode::WINDOWED));
|
||||
}
|
||||
}
|
||||
|
||||
void WindowControl::nextFrame(bool waitForRefresh) {
|
||||
const auto& settings = engine.getSettings();
|
||||
auto& window = engine.getWindow();
|
||||
auto& input = engine.getInput();
|
||||
window.setFramerate(
|
||||
window.isIconified() && settings.display.limitFpsIconified.get()
|
||||
? 20
|
||||
: settings.display.framerate.get()
|
||||
);
|
||||
window.swapBuffers();
|
||||
input.pollEvents(waitForRefresh && !window.checkShouldRefresh());
|
||||
}
|
||||
26
src/engine/WindowControl.hpp
Normal file
26
src/engine/WindowControl.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Window;
|
||||
class Input;
|
||||
class Engine;
|
||||
|
||||
class WindowControl {
|
||||
public:
|
||||
struct Result {
|
||||
std::unique_ptr<Window> window;
|
||||
std::unique_ptr<Input> input;
|
||||
};
|
||||
WindowControl(Engine& engine);
|
||||
|
||||
Result initialize();
|
||||
|
||||
void nextFrame(bool waitForRefresh);
|
||||
|
||||
void saveScreenshot();
|
||||
|
||||
void toggleFullscreen();
|
||||
private:
|
||||
Engine& engine;
|
||||
};
|
||||
|
|
@ -62,7 +62,7 @@ void ContentGfxCache::refreshVariant(
|
|||
}
|
||||
}
|
||||
}
|
||||
models[def.rt.id] = std::move(model);
|
||||
models[modelKey(def.rt.id, variantIndex)] = std::move(model);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,8 +94,8 @@ void ContentGfxCache::refresh() {
|
|||
|
||||
ContentGfxCache::~ContentGfxCache() = default;
|
||||
|
||||
const model::Model& ContentGfxCache::getModel(blockid_t id) const {
|
||||
const auto& found = models.find(id);
|
||||
const model::Model& ContentGfxCache::getModel(blockid_t id, uint8_t variant) const {
|
||||
const auto& found = models.find(modelKey(id, variant));
|
||||
if (found == models.end()) {
|
||||
throw std::runtime_error("model not found");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,11 @@ class ContentGfxCache {
|
|||
|
||||
// array of block sides uv regions (6 per block)
|
||||
std::unique_ptr<UVRegion[]> sideregions;
|
||||
std::unordered_map<blockid_t, model::Model> models;
|
||||
std::unordered_map<uint64_t, model::Model> models;
|
||||
|
||||
static inline uint64_t modelKey(blockid_t id, uint8_t variant) {
|
||||
return (uint64_t(id) << 8) | uint64_t(variant & 0xFF);
|
||||
}
|
||||
|
||||
void refreshVariant(
|
||||
const Block& def,
|
||||
|
|
@ -53,7 +57,7 @@ public:
|
|||
return sideregions[getRegionIndex(id, variant, side, !dense)];
|
||||
}
|
||||
|
||||
const model::Model& getModel(blockid_t id) const;
|
||||
const model::Model& getModel(blockid_t id, uint8_t variant) const;
|
||||
|
||||
void refresh(const Block& block, const Atlas& atlas);
|
||||
|
||||
|
|
|
|||
|
|
@ -49,8 +49,7 @@ LevelFrontend::LevelFrontend(
|
|||
auto sound = rassets.get<audio::Sound>(material->stepsSound);
|
||||
glm::vec3 pos {};
|
||||
auto soundsCamera = currentPlayer->currentCamera.get();
|
||||
if (soundsCamera == currentPlayer->spCamera.get() ||
|
||||
soundsCamera == currentPlayer->tpCamera.get()) {
|
||||
if (currentPlayer->isCurrentCameraBuiltin()) {
|
||||
soundsCamera = currentPlayer->fpCamera.get();
|
||||
}
|
||||
bool relative = player == currentPlayer &&
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "graphics/ui/elements/TextBox.hpp"
|
||||
#include "graphics/ui/elements/TrackBar.hpp"
|
||||
#include "graphics/ui/elements/InputBindBox.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "graphics/render/WorldRenderer.hpp"
|
||||
#include "graphics/render/ParticlesRenderer.hpp"
|
||||
#include "graphics/render/ChunksRenderer.hpp"
|
||||
|
|
@ -43,10 +44,15 @@ static std::shared_ptr<Label> create_label(GUI& gui, wstringsupplier supplier) {
|
|||
return label;
|
||||
}
|
||||
|
||||
static bool should_keep_previous(GUI& gui) {
|
||||
return !gui.getInput().isCursorLocked();
|
||||
}
|
||||
|
||||
// TODO: move to xml
|
||||
// TODO: move to xml finally
|
||||
// TODO: move to xml finally
|
||||
// TODO: move to xml finally
|
||||
// TODO: move to xml finally
|
||||
std::shared_ptr<UINode> create_debug_panel(
|
||||
Engine& engine,
|
||||
Level& level,
|
||||
|
|
@ -135,7 +141,16 @@ std::shared_ptr<UINode> create_debug_panel(
|
|||
std::to_wstring(player.getId());
|
||||
}));
|
||||
panel->add(create_label(gui, [&]() -> std::wstring {
|
||||
const auto& vox = player.selection.vox;
|
||||
// TODO: move to xml finally
|
||||
static voxel prevVox = {BLOCK_VOID, {}};
|
||||
|
||||
auto vox = player.selection.vox;
|
||||
if (vox.id == BLOCK_VOID && should_keep_previous(gui)) {
|
||||
vox = prevVox;
|
||||
} else {
|
||||
prevVox = vox;
|
||||
}
|
||||
|
||||
std::wstringstream stream;
|
||||
stream << "r:" << vox.state.rotation << " s:"
|
||||
<< std::bitset<3>(vox.state.segment) << " u:"
|
||||
|
|
@ -148,8 +163,17 @@ std::shared_ptr<UINode> create_debug_panel(
|
|||
}
|
||||
}));
|
||||
panel->add(create_label(gui, [&]() -> std::wstring {
|
||||
const auto& selection = player.selection;
|
||||
// TODO: move to xml finally
|
||||
static CursorSelection prevSelection {};
|
||||
|
||||
auto selection = player.selection;
|
||||
const auto& vox = selection.vox;
|
||||
if (vox.id == BLOCK_VOID && should_keep_previous(gui)) {
|
||||
selection = prevSelection;
|
||||
} else {
|
||||
prevSelection = selection;
|
||||
}
|
||||
|
||||
if (vox.id == BLOCK_VOID) {
|
||||
return L"x: - y: - z: -";
|
||||
}
|
||||
|
|
@ -158,7 +182,16 @@ std::shared_ptr<UINode> create_debug_panel(
|
|||
L" z: " + std::to_wstring(selection.actualPosition.z);
|
||||
}));
|
||||
panel->add(create_label(gui, [&]() {
|
||||
// TODO: move to xml finally
|
||||
static entityid_t prevEid = ENTITY_NONE;
|
||||
|
||||
auto eid = player.getSelectedEntity();
|
||||
if (eid == ENTITY_NONE && should_keep_previous(gui)) {
|
||||
eid = prevEid;
|
||||
} else {
|
||||
prevEid = eid;
|
||||
}
|
||||
|
||||
if (eid == ENTITY_NONE) {
|
||||
return std::wstring {L"entity: -"};
|
||||
} else if (auto entity = level.entities->get(eid)) {
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
|
|||
gui.add(contentAccessPanel);
|
||||
|
||||
auto dplotter = std::make_shared<Plotter>(gui, 350, 250, 2000, 16);
|
||||
dplotter->setGravity(Gravity::bottom_right);
|
||||
dplotter->setGravity(Gravity::BOTTOM_RIGHT);
|
||||
dplotter->setInteractive(false);
|
||||
add(HudElement(HudElementMode::PERMANENT, nullptr, dplotter, true));
|
||||
|
||||
|
|
@ -469,6 +469,7 @@ void Hud::showExchangeSlot() {
|
|||
gui,
|
||||
SlotLayout(-1, glm::vec2(), false, false, nullptr, nullptr, nullptr)
|
||||
);
|
||||
exchangeSlot->setId("hud.exchange-slot");
|
||||
exchangeSlot->bind(exchangeSlotInv->getId(), exchangeSlotInv->getSlot(0), &content);
|
||||
exchangeSlot->setColor(glm::vec4());
|
||||
exchangeSlot->setInteractive(false);
|
||||
|
|
@ -756,3 +757,13 @@ void Hud::setAllowPause(bool flag) {
|
|||
}
|
||||
allowPause = flag;
|
||||
}
|
||||
|
||||
bool Hud::isOpen(const std::string& layoutid) const {
|
||||
for (const auto& element : elements) {
|
||||
auto doc = element.getDocument();
|
||||
if (doc && doc->getId() == layoutid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,6 +213,8 @@ public:
|
|||
|
||||
void setAllowPause(bool flag);
|
||||
|
||||
bool isOpen(const std::string& layoutid) const;
|
||||
|
||||
static bool showGeneratorMinimap;
|
||||
|
||||
/// @brief Runtime updating debug visualization texture
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
#include "graphics/ui/elements/Menu.hpp"
|
||||
#include "graphics/ui/gui_util.hpp"
|
||||
#include "interfaces/Task.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "locale.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "screens/MenuScreen.hpp"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "core_defs.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "assets/Assets.hpp"
|
||||
#include "frontend/ContentGfxCache.hpp"
|
||||
#include "frontend/LevelFrontend.hpp"
|
||||
|
|
@ -75,13 +76,14 @@ LevelScreen::LevelScreen(
|
|||
engine, *controller, *renderer, assets, *player
|
||||
);
|
||||
|
||||
keepAlive(settings.graphics.backlight.observe([=](bool) {
|
||||
player->chunks->saveAndClear();
|
||||
renderer->clear();
|
||||
}));
|
||||
keepAlive(settings.graphics.denseRender.observe([=](bool) {
|
||||
auto resetChunks = [=](bool) {
|
||||
player->chunks->saveAndClear();
|
||||
renderer->clear();
|
||||
};
|
||||
keepAlive(settings.graphics.backlight.observe(resetChunks));
|
||||
keepAlive(settings.graphics.softLighting.observe(resetChunks));
|
||||
keepAlive(settings.graphics.denseRender.observe([=](bool flag) {
|
||||
resetChunks(flag);
|
||||
frontend->getContentGfxCache().refresh();
|
||||
}));
|
||||
keepAlive(settings.camera.fov.observe([=](double value) {
|
||||
|
|
@ -107,6 +109,7 @@ LevelScreen::~LevelScreen() {
|
|||
scripting::on_frontend_close();
|
||||
// unblock all bindings
|
||||
input.getBindings().enableAll();
|
||||
playerController->getPlayer()->chunks->saveAndClear();
|
||||
controller->onWorldQuit();
|
||||
engine.getPaths().setCurrentWorldFolder("");
|
||||
}
|
||||
|
|
@ -276,5 +279,6 @@ void LevelScreen::onEngineShutdown() {
|
|||
if (hud->isInventoryOpen()) {
|
||||
hud->closeInventory();
|
||||
}
|
||||
controller->processBeforeQuit();
|
||||
controller->saveWorld();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@
|
|||
#include "window/Camera.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
|
||||
MenuScreen::MenuScreen(Engine& engine) : Screen(engine) {
|
||||
uicamera =
|
||||
std::make_unique<Camera>(glm::vec3(), engine.getWindow().getSize().y);
|
||||
MenuScreen::MenuScreen(Engine& engine)
|
||||
: Screen(engine),
|
||||
uicamera(
|
||||
std::make_unique<Camera>(glm::vec3(), engine.getWindow().getSize().y)
|
||||
) {
|
||||
uicamera->perspective = false;
|
||||
uicamera->near = -1.0f;
|
||||
uicamera->far = 1.0f;
|
||||
|
|
@ -24,7 +26,7 @@ MenuScreen::MenuScreen(Engine& engine) : Screen(engine) {
|
|||
MenuScreen::~MenuScreen() = default;
|
||||
|
||||
void MenuScreen::onOpen() {
|
||||
engine.getContentControl().resetContent();
|
||||
engine.getContentControl().resetContent({});
|
||||
|
||||
auto menu = engine.getGUI().getMenu();
|
||||
menu->reset();
|
||||
|
|
|
|||
|
|
@ -49,6 +49,14 @@ ImageData* Atlas::getImage() const {
|
|||
return image.get();
|
||||
}
|
||||
|
||||
std::shared_ptr<Texture> Atlas::shareTexture() const {
|
||||
return texture;
|
||||
}
|
||||
|
||||
std::shared_ptr<ImageData> Atlas::shareImageData() const {
|
||||
return image;
|
||||
}
|
||||
|
||||
void AtlasBuilder::add(const std::string& name, std::unique_ptr<ImageData> image) {
|
||||
entries.push_back(atlasentry{name, std::shared_ptr<ImageData>(image.release())});
|
||||
names.insert(name);
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ class ImageData;
|
|||
class Texture;
|
||||
|
||||
class Atlas {
|
||||
std::unique_ptr<Texture> texture;
|
||||
std::unique_ptr<ImageData> image;
|
||||
std::shared_ptr<Texture> texture;
|
||||
std::shared_ptr<ImageData> image;
|
||||
std::unordered_map<std::string, UVRegion> regions;
|
||||
public:
|
||||
/// @param image atlas raster
|
||||
|
|
@ -36,6 +36,9 @@ public:
|
|||
|
||||
Texture* getTexture() const;
|
||||
ImageData* getImage() const;
|
||||
|
||||
std::shared_ptr<Texture> shareTexture() const;
|
||||
std::shared_ptr<ImageData> shareImageData() const;
|
||||
};
|
||||
|
||||
struct atlasentry {
|
||||
|
|
|
|||
|
|
@ -95,6 +95,17 @@ void ImageData::blit(const ImageData& image, int x, int y) {
|
|||
throw std::runtime_error("mismatching format");
|
||||
}
|
||||
|
||||
std::unique_ptr<ImageData> ImageData::cropped(int x, int y, int width, int height) const {
|
||||
width = std::min<int>(width, this->width - x);
|
||||
height = std::min<int>(height, this->height - y);
|
||||
if (width <= 0 || height <= 0) {
|
||||
throw std::runtime_error("invalid crop dimensions");
|
||||
}
|
||||
auto subImage = std::make_unique<ImageData>(format, width, height);
|
||||
subImage->blitMatchingFormat(*this, -x, -y);
|
||||
return subImage;
|
||||
}
|
||||
|
||||
static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) {
|
||||
const int left = 0;
|
||||
const int right = width;
|
||||
|
|
@ -409,6 +420,125 @@ void ImageData::fixAlphaColor() {
|
|||
}
|
||||
}
|
||||
|
||||
static void check_matching(const ImageData& a, const ImageData& b) {
|
||||
if (b.getWidth() != a.getWidth() ||
|
||||
b.getHeight() != a.getHeight() ||
|
||||
b.getFormat() != a.getFormat()) {
|
||||
throw std::runtime_error("image sizes or formats do not match");
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::mulColor(const glm::ivec4& color) {
|
||||
uint comps;
|
||||
switch (format) {
|
||||
case ImageFormat::rgb888: comps = 3; break;
|
||||
case ImageFormat::rgba8888: comps = 4; break;
|
||||
default:
|
||||
throw std::runtime_error("only unsigned byte formats supported");
|
||||
}
|
||||
for (uint y = 0; y < height; y++) {
|
||||
for (uint x = 0; x < width; x++) {
|
||||
uint idx = (y * width + x) * comps;
|
||||
for (uint c = 0; c < comps; c++) {
|
||||
float val = static_cast<float>(data[idx + c]) * color[c] / 255.0f;
|
||||
data[idx + c] =
|
||||
static_cast<ubyte>(std::min(std::max(val, 0.0f), 255.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::addColor(const ImageData& other, int multiplier) {
|
||||
check_matching(*this, other);
|
||||
|
||||
uint comps;
|
||||
switch (format) {
|
||||
case ImageFormat::rgb888: comps = 3; break;
|
||||
case ImageFormat::rgba8888: comps = 4; break;
|
||||
default:
|
||||
throw std::runtime_error("only unsigned byte formats supported");
|
||||
}
|
||||
for (uint y = 0; y < height; y++) {
|
||||
for (uint x = 0; x < width; x++) {
|
||||
uint idx = (y * width + x) * comps;
|
||||
for (uint c = 0; c < comps; c++) {
|
||||
int val = data[idx + c] + other.data[idx + c] * multiplier;
|
||||
data[idx + c] =
|
||||
static_cast<ubyte>(std::min(std::max(val, 0), 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::extend(int newWidth, int newHeight) {
|
||||
size_t comps;
|
||||
switch (format) {
|
||||
case ImageFormat::rgb888: comps = 3; break;
|
||||
case ImageFormat::rgba8888: comps = 4; break;
|
||||
default:
|
||||
throw std::runtime_error("only unsigned byte formats supported");
|
||||
}
|
||||
auto newData = std::make_unique<ubyte[]>(newWidth * newHeight * comps);
|
||||
for (uint y = 0; y < newHeight; y++) {
|
||||
for (uint x = 0; x < newWidth; x++) {
|
||||
for (size_t c = 0; c < comps; c++) {
|
||||
if (x < width && y < height) {
|
||||
newData[(y * newWidth + x) * comps + c] =
|
||||
data[(y * width + x) * comps + c];
|
||||
} else {
|
||||
newData[(y * newWidth + x) * comps + c] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data = std::move(newData);
|
||||
width = newWidth;
|
||||
height = newHeight;
|
||||
}
|
||||
|
||||
void ImageData::addColor(const glm::ivec4& color, int multiplier) {
|
||||
uint comps;
|
||||
switch (format) {
|
||||
case ImageFormat::rgb888: comps = 3; break;
|
||||
case ImageFormat::rgba8888: comps = 4; break;
|
||||
default:
|
||||
throw std::runtime_error("only unsigned byte formats supported");
|
||||
}
|
||||
for (uint y = 0; y < height; y++) {
|
||||
for (uint x = 0; x < width; x++) {
|
||||
uint idx = (y * width + x) * comps;
|
||||
for (uint c = 0; c < comps; c++) {
|
||||
int val = data[idx + c] + color[c] * multiplier;
|
||||
data[idx + c] =
|
||||
static_cast<ubyte>(std::min(std::max(val, 0), 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::mulColor(const ImageData& other) {
|
||||
check_matching(*this, other);
|
||||
|
||||
uint comps;
|
||||
switch (format) {
|
||||
case ImageFormat::rgb888: comps = 3; break;
|
||||
case ImageFormat::rgba8888: comps = 4; break;
|
||||
default:
|
||||
throw std::runtime_error("only unsigned byte formats supported");
|
||||
}
|
||||
for (uint y = 0; y < height; y++) {
|
||||
for (uint x = 0; x < width; x++) {
|
||||
uint idx = (y * width + x) * comps;
|
||||
for (uint c = 0; c < comps; c++) {
|
||||
float val = static_cast<float>(data[idx + c]) *
|
||||
static_cast<float>(other.data[idx + c]) / 255.0f;
|
||||
data[idx + c] =
|
||||
static_cast<ubyte>(std::min(std::max(val, 0.0f), 255.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<ImageData> add_atlas_margins(ImageData* image, int grid_size) {
|
||||
// RGBA is only supported
|
||||
assert(image->getFormat() == ImageFormat::rgba8888);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@ public:
|
|||
void blit(const ImageData& image, int x, int y);
|
||||
void extrude(int x, int y, int w, int h);
|
||||
void fixAlphaColor();
|
||||
void mulColor(const glm::ivec4& color);
|
||||
void mulColor(const ImageData& other);
|
||||
void addColor(const glm::ivec4& color, int multiplier);
|
||||
void addColor(const ImageData& other, int multiplier);
|
||||
void extend(int newWidth, int newHeight);
|
||||
|
||||
std::unique_ptr<ImageData> cropped(int x, int y, int width, int height) const;
|
||||
|
||||
ubyte* getData() const {
|
||||
return data.get();
|
||||
|
|
|
|||
|
|
@ -44,13 +44,21 @@ void Texture::unbind() const {
|
|||
void Texture::reload(const ImageData& image) {
|
||||
width = image.getWidth();
|
||||
height = image.getHeight();
|
||||
reload(image.getData());
|
||||
reload(image.getData(), width, height);
|
||||
}
|
||||
|
||||
void Texture::reload(const ubyte* data) {
|
||||
void Texture::reload(const ubyte* data, uint width, uint height) {
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data));
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) {
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.getData());
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ public:
|
|||
|
||||
virtual void bind() const;
|
||||
virtual void unbind() const;
|
||||
void reload(const ubyte* data);
|
||||
void reload(const ubyte* data, uint w, uint h);
|
||||
void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h);
|
||||
|
||||
void setNearestFilter();
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ std::unique_ptr<ImageData> BlocksPreview::draw(
|
|||
glm::vec3 poff = glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
offset.y += (1.0f - hitbox).y * 0.5f;
|
||||
shader.uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset));
|
||||
const auto& model = cache.getModel(def.rt.id);
|
||||
const auto& model = cache.getModel(def.rt.id, 0);
|
||||
|
||||
for (const auto& mesh : model.meshes) {
|
||||
for (const auto& vertex : mesh.vertices) {
|
||||
|
|
@ -137,5 +137,5 @@ std::unique_ptr<Atlas> BlocksPreview::build(
|
|||
builder.add(def.name, draw(cache, shader, fbo, batch, def, iconSize));
|
||||
}
|
||||
fbo.unbind();
|
||||
return builder.build(2);
|
||||
return builder.build(ATLAS_EXTRUSION);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include "lighting/Lightmap.hpp"
|
||||
#include "frontend/ContentGfxCache.hpp"
|
||||
|
||||
const glm::vec3 BlocksRenderer::SUN_VECTOR(0.528265f, 0.833149f, -0.163704f);
|
||||
const glm::vec3 BlocksRenderer::SUN_VECTOR(0.528265, 0.833149, -0.163704);
|
||||
const float DIRECTIONAL_LIGHT_FACTOR = 0.3f;
|
||||
|
||||
BlocksRenderer::BlocksRenderer(
|
||||
|
|
@ -35,8 +35,7 @@ BlocksRenderer::BlocksRenderer(
|
|||
blockDefsCache = content.getIndices()->blocks.getDefs();
|
||||
}
|
||||
|
||||
BlocksRenderer::~BlocksRenderer() {
|
||||
}
|
||||
BlocksRenderer::~BlocksRenderer() = default;
|
||||
|
||||
/// Basic vertex add method
|
||||
void BlocksRenderer::vertex(
|
||||
|
|
@ -308,7 +307,7 @@ void BlocksRenderer::blockCustomModel(
|
|||
Z = orient.axes[2];
|
||||
}
|
||||
|
||||
const auto& model = cache.getModel(block.rt.id);
|
||||
const auto& model = cache.getModel(block.rt.id, block.getVariantIndex(states.userbits));
|
||||
for (const auto& mesh : model.meshes) {
|
||||
if (vertexCount + mesh.vertices.size() >= capacity) {
|
||||
overflow = true;
|
||||
|
|
@ -480,6 +479,7 @@ void BlocksRenderer::render(
|
|||
) {
|
||||
bool denseRender = this->denseRender;
|
||||
bool densePass = this->densePass;
|
||||
bool enableAO = settings.graphics.softLighting.get();
|
||||
for (const auto drawGroup : *content.drawGroups) {
|
||||
int begin = beginEnds[drawGroup][0];
|
||||
if (begin == 0) {
|
||||
|
|
@ -516,7 +516,7 @@ void BlocksRenderer::render(
|
|||
switch (def.getModel(state.userbits).type) {
|
||||
case BlockModelType::BLOCK:
|
||||
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
|
||||
def.ambientOcclusion);
|
||||
def.ambientOcclusion && enableAO);
|
||||
break;
|
||||
case BlockModelType::XSPRITE: {
|
||||
if (!denseRender)
|
||||
|
|
@ -527,7 +527,7 @@ void BlocksRenderer::render(
|
|||
case BlockModelType::AABB: {
|
||||
if (!denseRender)
|
||||
blockAABB({x, y, z}, texfaces, &def, vox.state.rotation,
|
||||
!def.shadeless, def.ambientOcclusion);
|
||||
!def.shadeless, def.ambientOcclusion && enableAO);
|
||||
break;
|
||||
}
|
||||
case BlockModelType::CUSTOM: {
|
||||
|
|
@ -537,7 +537,7 @@ void BlocksRenderer::render(
|
|||
def,
|
||||
vox.state,
|
||||
!def.shadeless,
|
||||
def.ambientOcclusion
|
||||
def.ambientOcclusion && enableAO
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
@ -561,6 +561,7 @@ SortingMeshData BlocksRenderer::renderTranslucent(
|
|||
size_t totalSize = 0;
|
||||
|
||||
bool densePass = this->densePass;
|
||||
bool enableAO = settings.graphics.softLighting.get();
|
||||
for (const auto drawGroup : *content.drawGroups) {
|
||||
int begin = beginEnds[drawGroup][0];
|
||||
if (begin == 0) {
|
||||
|
|
@ -594,7 +595,7 @@ SortingMeshData BlocksRenderer::renderTranslucent(
|
|||
switch (def.getModel(state.userbits).type) {
|
||||
case BlockModelType::BLOCK:
|
||||
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
|
||||
def.ambientOcclusion);
|
||||
def.ambientOcclusion && enableAO);
|
||||
break;
|
||||
case BlockModelType::XSPRITE: {
|
||||
blockXSprite(x, y, z, glm::vec3(1.0f),
|
||||
|
|
@ -602,13 +603,24 @@ SortingMeshData BlocksRenderer::renderTranslucent(
|
|||
break;
|
||||
}
|
||||
case BlockModelType::AABB: {
|
||||
blockAABB({x, y, z}, texfaces, &def, vox.state.rotation,
|
||||
!def.shadeless, def.ambientOcclusion);
|
||||
blockAABB(
|
||||
{x, y, z},
|
||||
texfaces,
|
||||
&def,
|
||||
vox.state.rotation,
|
||||
!def.shadeless,
|
||||
def.ambientOcclusion && enableAO
|
||||
);
|
||||
break;
|
||||
}
|
||||
case BlockModelType::CUSTOM: {
|
||||
blockCustomModel({x, y, z}, def, vox.state,
|
||||
!def.shadeless, def.ambientOcclusion);
|
||||
blockCustomModel(
|
||||
{x, y, z},
|
||||
def,
|
||||
vox.state,
|
||||
!def.shadeless,
|
||||
def.ambientOcclusion && enableAO
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include "logic/LevelController.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "audio/audio.hpp"
|
||||
#include "maths/util.hpp"
|
||||
|
|
|
|||
|
|
@ -30,14 +30,14 @@ namespace gui {
|
|||
glm::vec2 size,
|
||||
glm::vec4 padding = glm::vec4(0.0f),
|
||||
float interval = 2.0f,
|
||||
Orientation orientation = Orientation::vertical
|
||||
Orientation orientation = Orientation::VERTICAL
|
||||
)
|
||||
: Container(gui, std::move(size)),
|
||||
padding(std::move(padding)),
|
||||
interval(interval) {
|
||||
}
|
||||
|
||||
Orientation orientation = Orientation::vertical;
|
||||
Orientation orientation = Orientation::VERTICAL;
|
||||
glm::vec4 padding;
|
||||
float interval = 2.0f;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ Button::Button(
|
|||
GUI& gui,
|
||||
const std::wstring& text,
|
||||
glm::vec4 padding,
|
||||
const onaction& action,
|
||||
const OnAction& action,
|
||||
glm::vec2 size
|
||||
)
|
||||
: Panel(gui, size, padding, 0.0f) {
|
||||
|
|
@ -42,12 +42,12 @@ Button::Button(
|
|||
}
|
||||
|
||||
if (action) {
|
||||
listenAction(action);
|
||||
listenClick(action);
|
||||
}
|
||||
setScrollable(false);
|
||||
|
||||
label = std::make_shared<Label>(gui, text);
|
||||
label->setAlign(Align::center);
|
||||
label->setAlign(Align::CENTER);
|
||||
label->setSize(getContentSize());
|
||||
label->setInteractive(false);
|
||||
add(label);
|
||||
|
|
@ -95,5 +95,5 @@ Align Button::getTextAlign() const {
|
|||
if (label) {
|
||||
return label->getAlign();
|
||||
}
|
||||
return Align::left;
|
||||
return Align::LEFT;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace gui {
|
|||
GUI& gui,
|
||||
const std::wstring& text,
|
||||
glm::vec4 padding,
|
||||
const onaction& action,
|
||||
const OnAction& action,
|
||||
glm::vec2 size = glm::vec2(-1)
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
|
||||
gui::Canvas::Canvas(GUI& gui, ImageFormat inFormat, glm::uvec2 inSize)
|
||||
: UINode(gui, inSize) {
|
||||
auto data = std::make_shared<ImageData>(inFormat, inSize.x, inSize.y);
|
||||
mTexture = Texture::from(data.get());
|
||||
mData = std::move(data);
|
||||
gui::Canvas::Canvas(GUI& gui, ImageFormat format, glm::uvec2 size)
|
||||
: UINode(gui, size) {
|
||||
auto data = std::make_shared<ImageData>(format, size.x, size.y);
|
||||
texture = Texture::from(data.get());
|
||||
this->data = std::move(data);
|
||||
}
|
||||
|
||||
void gui::Canvas::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
|
|
@ -16,6 +16,12 @@ void gui::Canvas::draw(const DrawContext& pctx, const Assets& assets) {
|
|||
auto col = calcColor();
|
||||
|
||||
auto batch = pctx.getBatch2D();
|
||||
batch->texture(mTexture.get());
|
||||
batch->texture(texture.get());
|
||||
batch->rect(pos.x, pos.y, size.x, size.y, 0, 0, 0, {}, false, false, col);
|
||||
}
|
||||
|
||||
void gui::Canvas::setSize(const glm::vec2& size) {
|
||||
UINode::setSize(size);
|
||||
data->extend(size.x, size.y);
|
||||
texture->reload(*data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,21 +9,23 @@ class Texture;
|
|||
namespace gui {
|
||||
class Canvas final : public UINode {
|
||||
public:
|
||||
explicit Canvas(GUI& gui, ImageFormat inFormat, glm::uvec2 inSize);
|
||||
explicit Canvas(GUI& gui, ImageFormat format, glm::uvec2 size);
|
||||
|
||||
~Canvas() override = default;
|
||||
|
||||
void draw(const DrawContext& pctx, const Assets& assets) override;
|
||||
|
||||
[[nodiscard]] auto texture() const {
|
||||
return mTexture;
|
||||
void setSize(const glm::vec2& size) override;
|
||||
|
||||
[[nodiscard]] auto getTexture() const {
|
||||
return texture;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto data() const {
|
||||
return mData;
|
||||
[[nodiscard]] auto getData() const {
|
||||
return data;
|
||||
}
|
||||
private:
|
||||
std::shared_ptr<::Texture> mTexture;
|
||||
std::shared_ptr<ImageData> mData;
|
||||
std::shared_ptr<::Texture> texture;
|
||||
std::shared_ptr<ImageData> data;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ FullCheckBox::FullCheckBox(
|
|||
checkbox(std::make_shared<CheckBox>(gui, checked)),
|
||||
label(std::make_shared<Label>(gui, text)) {
|
||||
setColor(glm::vec4(0.0f));
|
||||
setOrientation(Orientation::horizontal);
|
||||
setOrientation(Orientation::HORIZONTAL);
|
||||
|
||||
add(checkbox);
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ namespace gui {
|
|||
checkbox->setTooltip(text);
|
||||
}
|
||||
|
||||
virtual void setSize(glm::vec2 new_size) override {
|
||||
virtual void setSize(const glm::vec2& new_size) override {
|
||||
Panel::setSize(new_size);
|
||||
checkbox->setSize(glm::vec2(size.y, size.y));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/Batch2D.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "../GUI.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
|
@ -77,6 +79,10 @@ void Container::act(float delta) {
|
|||
node->act(delta);
|
||||
}
|
||||
}
|
||||
if (!intervalEvents.empty()) {
|
||||
// TODO: make it interval-based
|
||||
gui.getWindow().setShouldRefresh();
|
||||
}
|
||||
for (IntervalEvent& event : intervalEvents) {
|
||||
event.timer += delta;
|
||||
if (event.timer > event.interval) {
|
||||
|
|
@ -170,6 +176,7 @@ void Container::add(const std::shared_ptr<UINode>& node) {
|
|||
parent->setMustRefresh();
|
||||
parent = parent->getParent();
|
||||
}
|
||||
gui.getWindow().setShouldRefresh();
|
||||
}
|
||||
|
||||
void Container::add(const std::shared_ptr<UINode>& node, glm::vec2 pos) {
|
||||
|
|
@ -203,11 +210,11 @@ void Container::clear() {
|
|||
refresh();
|
||||
}
|
||||
|
||||
void Container::listenInterval(float interval, ontimeout callback, int repeat) {
|
||||
void Container::listenInterval(float interval, OnTimeOut callback, int repeat) {
|
||||
intervalEvents.push_back({std::move(callback), interval, 0.0f, repeat});
|
||||
}
|
||||
|
||||
void Container::setSize(glm::vec2 size) {
|
||||
void Container::setSize(const glm::vec2& size) {
|
||||
if (size == getSize()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,9 +36,11 @@ namespace gui {
|
|||
virtual void remove(const std::string& id);
|
||||
virtual void scrolled(int value) override;
|
||||
virtual void setScrollable(bool flag);
|
||||
void listenInterval(float interval, ontimeout callback, int repeat=-1);
|
||||
virtual glm::vec2 getContentOffset() override {return glm::vec2(0.0f, scroll);};
|
||||
virtual void setSize(glm::vec2 size) override;
|
||||
void listenInterval(float interval, OnTimeOut callback, int repeat=-1);
|
||||
virtual glm::vec2 getContentOffset() const override {
|
||||
return glm::vec2(0.0f, scroll);
|
||||
};
|
||||
virtual void setSize(const glm::vec2& size) override;
|
||||
virtual int getScrollStep() const;
|
||||
virtual void setScrollStep(int step);
|
||||
virtual void refresh() override;
|
||||
|
|
|
|||
|
|
@ -36,15 +36,14 @@ void InlineFrame::setDocument(const std::shared_ptr<UiDocument>& document) {
|
|||
}
|
||||
|
||||
void InlineFrame::act(float delta) {
|
||||
Container::act(delta);
|
||||
if (document || src.empty()) {
|
||||
return;
|
||||
if (document == nullptr && !src.empty()) {
|
||||
const auto& assets = *gui.getEngine().getAssets();
|
||||
setDocument(assets.getShared<UiDocument>(src));
|
||||
}
|
||||
const auto& assets = *gui.getEngine().getAssets();
|
||||
setDocument(assets.getShared<UiDocument>(src));
|
||||
Container::act(delta);
|
||||
}
|
||||
|
||||
void InlineFrame::setSize(glm::vec2 size) {
|
||||
void InlineFrame::setSize(const glm::vec2& size) {
|
||||
Container::setSize(size);
|
||||
if (root) {
|
||||
root->setSize(size);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
class UiDocument;
|
||||
|
||||
namespace gui {
|
||||
class InlineFrame : public Container {
|
||||
class InlineFrame final : public Container {
|
||||
public:
|
||||
explicit InlineFrame(GUI& gui);
|
||||
virtual ~InlineFrame();
|
||||
|
|
@ -14,7 +14,7 @@ namespace gui {
|
|||
void setDocument(const std::shared_ptr<UiDocument>& document);
|
||||
|
||||
void act(float delta) override;
|
||||
void setSize(glm::vec2 size) override;
|
||||
void setSize(const glm::vec2& size) override;
|
||||
|
||||
const std::string& getSrc() const;
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
namespace gui {
|
||||
class Label;
|
||||
|
||||
class InputBindBox : public Panel {
|
||||
class InputBindBox final : public Panel {
|
||||
protected:
|
||||
Binding& binding;
|
||||
glm::vec4 focusedColor {0.1f, 0.15f, 0.35f, 0.75f};
|
||||
|
|
@ -15,13 +15,13 @@ namespace gui {
|
|||
GUI& gui, Binding& binding, glm::vec4 padding = glm::vec4(6.0f)
|
||||
);
|
||||
|
||||
virtual void drawBackground(
|
||||
void drawBackground(
|
||||
const DrawContext& pctx, const Assets& assets
|
||||
) override;
|
||||
|
||||
virtual void clicked(Mousecode button) override;
|
||||
virtual void keyPressed(Keycode key) override;
|
||||
virtual bool isFocuskeeper() const override {
|
||||
void clicked(Mousecode button) override;
|
||||
void keyPressed(Keycode key) override;
|
||||
bool isFocuskeeper() const override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -528,14 +528,14 @@ void InventoryView::setSelected(int index) {
|
|||
}
|
||||
}
|
||||
|
||||
void InventoryView::setPos(glm::vec2 pos) {
|
||||
void InventoryView::setPos(const glm::vec2& pos) {
|
||||
Container::setPos(pos - origin);
|
||||
}
|
||||
|
||||
void InventoryView::setOrigin(glm::vec2 origin) {
|
||||
void InventoryView::setOrigin(const glm::vec2& origin) {
|
||||
this->origin = origin;
|
||||
}
|
||||
|
||||
glm::vec2 InventoryView::getOrigin() const {
|
||||
const glm::vec2& InventoryView::getOrigin() const {
|
||||
return origin;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ namespace gui {
|
|||
static inline std::string EXCHANGE_SLOT_NAME = "exchange-slot";
|
||||
};
|
||||
|
||||
class InventoryView : public gui::Container {
|
||||
class InventoryView final : public gui::Container {
|
||||
const Content* content = nullptr;
|
||||
|
||||
std::shared_ptr<Inventory> inventory;
|
||||
|
|
@ -120,10 +120,10 @@ namespace gui {
|
|||
InventoryView(GUI& gui);
|
||||
virtual ~InventoryView();
|
||||
|
||||
virtual void setPos(glm::vec2 pos) override;
|
||||
void setPos(const glm::vec2& pos) override;
|
||||
|
||||
void setOrigin(glm::vec2 origin);
|
||||
glm::vec2 getOrigin() const;
|
||||
void setOrigin(const glm::vec2& origin);
|
||||
const glm::vec2& getOrigin() const;
|
||||
|
||||
void setSelected(int index);
|
||||
|
||||
|
|
|
|||
|
|
@ -230,14 +230,14 @@ void Label::draw(const DrawContext& pctx, const Assets& assets) {
|
|||
|
||||
glm::vec2 pos = calcPos();
|
||||
switch (align) {
|
||||
case Align::left: break;
|
||||
case Align::center: pos.x += (size.x-newsize.x)*0.5f; break;
|
||||
case Align::right: pos.x += size.x-newsize.x; break;
|
||||
case Align::LEFT: break;
|
||||
case Align::CENTER: pos.x += (size.x-newsize.x)*0.5f; break;
|
||||
case Align::RIGHT: pos.x += size.x-newsize.x; break;
|
||||
}
|
||||
switch (valign) {
|
||||
case Align::top: break;
|
||||
case Align::center: pos.y += (size.y-newsize.y)*0.5f; break;
|
||||
case Align::bottom: pos.y += size.y-newsize.y; break;
|
||||
case Align::TOP: break;
|
||||
case Align::CENTER: pos.y += (size.y-newsize.y)*0.5f; break;
|
||||
case Align::BOTTOM: pos.y += size.y-newsize.y; break;
|
||||
}
|
||||
textYOffset = pos.y-calcPos().y;
|
||||
totalLineHeight = lineHeight;
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ namespace gui {
|
|||
float lineInterval = 1.5f;
|
||||
|
||||
/// @brief Vertical alignment (only when multiline is set to false)
|
||||
Align valign = Align::center;
|
||||
Align valign = Align::CENTER;
|
||||
|
||||
/// @brief Line separators and wrapping will be ignored if set to false
|
||||
bool multiline = false;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ void Panel::refresh() {
|
|||
float x = padding.x;
|
||||
float y = padding.y;
|
||||
glm::vec2 size = getSize();
|
||||
if (orientation == Orientation::vertical) {
|
||||
if (orientation == Orientation::VERTICAL) {
|
||||
float maxw = size.x;
|
||||
for (auto& node : nodes) {
|
||||
const glm::vec4 margin = node->getMargin();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ SelectBox::SelectBox(
|
|||
: Button(gui, selected.text, padding, nullptr, glm::vec2(contentWidth, -1)),
|
||||
options(std::move(options)) {
|
||||
|
||||
listenAction([this](GUI& gui) {
|
||||
listenClick([this](GUI& gui) {
|
||||
auto panel = std::make_shared<Panel>(gui, getSize());
|
||||
panel->setPos(calcPos() + glm::vec2(0, size.y));
|
||||
for (const auto& option : this->options) {
|
||||
|
|
@ -41,7 +41,7 @@ SelectBox::SelectBox(
|
|||
});
|
||||
}
|
||||
|
||||
void SelectBox::listenChange(onstringchange&& callback) {
|
||||
void SelectBox::listenChange(OnStringChange&& callback) {
|
||||
changeCallbacks.listen(std::move(callback));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace gui {
|
|||
const glm::vec4& padding
|
||||
);
|
||||
|
||||
void listenChange(onstringchange&& callback);
|
||||
void listenChange(OnStringChange&& callback);
|
||||
|
||||
void setSelected(const Option& selected);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using namespace gui;
|
|||
SplitBox::SplitBox(GUI& gui, const glm::vec2& size, float splitPos, Orientation orientation)
|
||||
: BasePanel(gui, size, glm::vec4(), 4.0f, orientation), splitPos(splitPos) {
|
||||
setCursor(
|
||||
orientation == Orientation::vertical ? CursorShape::NS_RESIZE
|
||||
orientation == Orientation::VERTICAL ? CursorShape::NS_RESIZE
|
||||
: CursorShape::EW_RESIZE
|
||||
);
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ void SplitBox::mouseMove(int x, int y) {
|
|||
auto size = getSize();
|
||||
|
||||
glm::ivec2 cursor(x - pos.x, y - pos.y);
|
||||
int axis = orientation == Orientation::vertical;
|
||||
int axis = orientation == Orientation::VERTICAL;
|
||||
|
||||
int v = cursor[axis];
|
||||
v = std::max(std::min(static_cast<int>(size[axis]) - 10, v), 10);
|
||||
|
|
@ -61,7 +61,7 @@ void SplitBox::refresh() {
|
|||
nodeA->setPos(glm::vec2(padding));
|
||||
|
||||
const auto& p = padding;
|
||||
if (orientation == Orientation::vertical) {
|
||||
if (orientation == Orientation::VERTICAL) {
|
||||
float splitPos = this->splitPos * size.y;
|
||||
nodeA->setSize({size.x-p.x-p.z, splitPos - sepRadius - p.y});
|
||||
nodeB->setSize({size.x-p.x-p.z, size.y - splitPos - sepRadius - p.w});
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ TextBox::TextBox(GUI& gui, std::wstring placeholder, glm::vec4 padding)
|
|||
lineNumbersLabel->setSize(
|
||||
size - glm::vec2(padding.z + padding.x, padding.w + padding.y)
|
||||
);
|
||||
lineNumbersLabel->setVerticalAlign(Align::top);
|
||||
lineNumbersLabel->setVerticalAlign(Align::TOP);
|
||||
add(lineNumbersLabel);
|
||||
|
||||
setHoverColor(glm::vec4(0.05f, 0.1f, 0.2f, 0.75f));
|
||||
|
|
@ -231,7 +231,7 @@ TextBox::~TextBox() = default;
|
|||
void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
Container::draw(pctx, assets);
|
||||
|
||||
if (!isFocused()) {
|
||||
if (!isFocused() && !keepLineSelection) {
|
||||
return;
|
||||
}
|
||||
const auto& labelText = getText();
|
||||
|
|
@ -252,7 +252,7 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
|||
|
||||
float time = gui.getWindow().time();
|
||||
|
||||
if (editable && static_cast<int>((time - caretLastMove) * 2) % 2 == 0) {
|
||||
if (isFocused() && editable && static_cast<int>((time - caretLastMove) * 2) % 2 == 0) {
|
||||
uint line = label->getLineByTextIndex(caret);
|
||||
uint lcaret = caret - label->getTextLineOffset(line);
|
||||
int width = rawTextCache.metrics.calcWidth(input, 0, lcaret);
|
||||
|
|
@ -308,42 +308,44 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
|||
}
|
||||
}
|
||||
|
||||
if (isFocused() && multiline) {
|
||||
auto selectionCtx = subctx.sub(batch);
|
||||
selectionCtx.setBlendMode(BlendMode::addition);
|
||||
|
||||
batch->setColor(glm::vec4(1, 1, 1, 0.1f));
|
||||
|
||||
uint line = label->getLineByTextIndex(caret);
|
||||
while (label->isFakeLine(line)) {
|
||||
line--;
|
||||
}
|
||||
do {
|
||||
int lineY = label->getLineYOffset(line);
|
||||
|
||||
batch->setColor(glm::vec4(1, 1, 1, 0.05f));
|
||||
if (showLineNumbers) {
|
||||
batch->rect(
|
||||
lcoord.x - 8,
|
||||
lcoord.y + lineY,
|
||||
label->getSize().x,
|
||||
lineHeight
|
||||
);
|
||||
batch->setColor(glm::vec4(1, 1, 1, 0.10f));
|
||||
batch->rect(
|
||||
lcoord.x - LINE_NUMBERS_PANE_WIDTH,
|
||||
lcoord.y + lineY,
|
||||
LINE_NUMBERS_PANE_WIDTH - 8,
|
||||
lineHeight
|
||||
);
|
||||
} else {
|
||||
batch->rect(
|
||||
lcoord.x, lcoord.y + lineY, label->getSize().x, lineHeight
|
||||
);
|
||||
}
|
||||
line++;
|
||||
} while (line < label->getLinesNumber() && label->isFakeLine(line));
|
||||
if (!multiline) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto selectionCtx = subctx.sub(batch);
|
||||
selectionCtx.setBlendMode(BlendMode::addition);
|
||||
|
||||
batch->setColor(glm::vec4(1, 1, 1, 0.1f));
|
||||
|
||||
uint line = label->getLineByTextIndex(caret);
|
||||
while (label->isFakeLine(line)) {
|
||||
line--;
|
||||
}
|
||||
do {
|
||||
int lineY = label->getLineYOffset(line);
|
||||
|
||||
batch->setColor(glm::vec4(1, 1, 1, 0.05f));
|
||||
if (showLineNumbers) {
|
||||
batch->rect(
|
||||
lcoord.x - 8,
|
||||
lcoord.y + lineY,
|
||||
label->getSize().x,
|
||||
lineHeight
|
||||
);
|
||||
batch->setColor(glm::vec4(1, 1, 1, 0.10f));
|
||||
batch->rect(
|
||||
lcoord.x - LINE_NUMBERS_PANE_WIDTH,
|
||||
lcoord.y + lineY,
|
||||
LINE_NUMBERS_PANE_WIDTH - 8,
|
||||
lineHeight
|
||||
);
|
||||
} else {
|
||||
batch->rect(
|
||||
lcoord.x, lcoord.y + lineY, label->getSize().x, lineHeight
|
||||
);
|
||||
}
|
||||
line++;
|
||||
} while (line < label->getLinesNumber() && label->isFakeLine(line));
|
||||
}
|
||||
|
||||
void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) {
|
||||
|
|
@ -484,7 +486,7 @@ void TextBox::erase(size_t start, size_t length) {
|
|||
setCaret(caret - length);
|
||||
}
|
||||
auto left = input.substr(0, start);
|
||||
auto right = input.substr(end);
|
||||
auto right = end >= input.length() ? L"" : input.substr(end);
|
||||
input = left + right;
|
||||
}
|
||||
|
||||
|
|
@ -565,7 +567,7 @@ bool TextBox::isValid() const {
|
|||
void TextBox::setMultiline(bool multiline) {
|
||||
this->multiline = multiline;
|
||||
label->setMultiline(multiline);
|
||||
label->setVerticalAlign(multiline ? Align::top : Align::center);
|
||||
label->setVerticalAlign(multiline ? Align::TOP : Align::CENTER);
|
||||
}
|
||||
|
||||
bool TextBox::isMultiline() const {
|
||||
|
|
@ -605,6 +607,14 @@ size_t TextBox::getSelectionEnd() const {
|
|||
return selectionEnd;
|
||||
}
|
||||
|
||||
void TextBox::setKeepLineSelection(bool flag) {
|
||||
keepLineSelection = flag;
|
||||
}
|
||||
|
||||
bool TextBox::isKeepLineSelection() const {
|
||||
return keepLineSelection;
|
||||
}
|
||||
|
||||
void TextBox::setOnEditStart(runnable oneditstart) {
|
||||
onEditStart = oneditstart;
|
||||
}
|
||||
|
|
@ -678,6 +688,11 @@ int TextBox::calcIndexAt(int x, int y) const {
|
|||
);
|
||||
}
|
||||
|
||||
int TextBox::getLineYOffset(int line) const {
|
||||
if (rawTextCache.fontId == 0) return 0;
|
||||
return label->getLineYOffset(line);
|
||||
}
|
||||
|
||||
static inline std::wstring get_alphabet(wchar_t c) {
|
||||
std::wstring alphabet {c};
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') {
|
||||
|
|
@ -1041,11 +1056,11 @@ void TextBox::keyPressed(Keycode key) {
|
|||
resetSelection();
|
||||
}
|
||||
}
|
||||
if (key == Keycode::Z) {
|
||||
if (editable && key == Keycode::Z) {
|
||||
historian->undo();
|
||||
refreshSyntax();
|
||||
}
|
||||
if (key == Keycode::Y) {
|
||||
if (editable && key == Keycode::Y) {
|
||||
historian->redo();
|
||||
refreshSyntax();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ namespace gui {
|
|||
bool editable = true;
|
||||
bool autoresize = false;
|
||||
bool showLineNumbers = false;
|
||||
bool keepLineSelection = false;
|
||||
std::string markup;
|
||||
std::string syntax;
|
||||
|
||||
|
|
@ -74,7 +75,6 @@ namespace gui {
|
|||
|
||||
size_t normalizeIndex(int index);
|
||||
|
||||
int calcIndexAt(int x, int y) const;
|
||||
void setTextOffset(uint x);
|
||||
bool eraseSelected();
|
||||
void resetSelection();
|
||||
|
|
@ -186,6 +186,9 @@ namespace gui {
|
|||
/// @return line position in text
|
||||
virtual size_t getLinePos(uint line) const;
|
||||
|
||||
int calcIndexAt(int x, int y) const;
|
||||
int getLineYOffset(int line) const;
|
||||
|
||||
/// @brief Check text with validator set with setTextValidator
|
||||
/// @return true if text is valid
|
||||
virtual bool validate();
|
||||
|
|
@ -220,6 +223,9 @@ namespace gui {
|
|||
size_t getSelectionStart() const;
|
||||
size_t getSelectionEnd() const;
|
||||
|
||||
void setKeepLineSelection(bool flag);
|
||||
bool isKeepLineSelection() const;
|
||||
|
||||
/// @brief Set runnable called on textbox focus
|
||||
virtual void setOnEditStart(runnable oneditstart);
|
||||
|
||||
|
|
|
|||
|
|
@ -64,41 +64,47 @@ UINode* UINode::getParent() const {
|
|||
return parent;
|
||||
}
|
||||
|
||||
UINode* UINode::listenAction(const onaction& action) {
|
||||
actions.listen(action);
|
||||
return this;
|
||||
void UINode::listenClick(OnAction action) {
|
||||
actions.listen(UIAction::CLICK, std::move(action));
|
||||
}
|
||||
|
||||
UINode* UINode::listenDoubleClick(const onaction& action) {
|
||||
doubleClickCallbacks.listen(action);
|
||||
return this;
|
||||
void UINode::listenRightClick(OnAction action) {
|
||||
actions.listen(UIAction::RIGHT_CLICK, std::move(action));
|
||||
}
|
||||
|
||||
UINode* UINode::listenFocus(const onaction& action) {
|
||||
focusCallbacks.listen(action);
|
||||
return this;
|
||||
void UINode::listenDoubleClick(OnAction action) {
|
||||
actions.listen(UIAction::DOUBLE_CLICK, std::move(action));
|
||||
}
|
||||
|
||||
UINode* UINode::listenDefocus(const onaction& action) {
|
||||
defocusCallbacks.listen(action);
|
||||
return this;
|
||||
void UINode::listenFocus(OnAction action) {
|
||||
actions.listen(UIAction::FOCUS, std::move(action));
|
||||
}
|
||||
|
||||
void UINode::listenDefocus(OnAction action) {
|
||||
actions.listen(UIAction::DEFOCUS, std::move(action));
|
||||
}
|
||||
|
||||
void UINode::click(int, int) {
|
||||
pressed = true;
|
||||
}
|
||||
|
||||
void UINode::clicked(Mousecode button) {
|
||||
if (button == Mousecode::BUTTON_2) {
|
||||
actions.notify(UIAction::RIGHT_CLICK, gui);
|
||||
}
|
||||
}
|
||||
|
||||
void UINode::doubleClick(int x, int y) {
|
||||
pressed = true;
|
||||
if (isInside(glm::vec2(x, y))) {
|
||||
doubleClickCallbacks.notify(gui);
|
||||
actions.notify(UIAction::DOUBLE_CLICK, gui);
|
||||
}
|
||||
}
|
||||
|
||||
void UINode::mouseRelease(int x, int y) {
|
||||
pressed = false;
|
||||
if (isInside(glm::vec2(x, y))) {
|
||||
actions.notify(gui);
|
||||
actions.notify(UIAction::CLICK, gui);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,12 +114,12 @@ bool UINode::isPressed() const {
|
|||
|
||||
void UINode::onFocus() {
|
||||
focused = true;
|
||||
focusCallbacks.notify(gui);
|
||||
actions.notify(UIAction::FOCUS, gui);
|
||||
}
|
||||
|
||||
void UINode::defocus() {
|
||||
focused = false;
|
||||
defocusCallbacks.notify(gui);
|
||||
actions.notify(UIAction::DEFOCUS, gui);
|
||||
}
|
||||
|
||||
bool UINode::isFocused() const {
|
||||
|
|
@ -197,7 +203,7 @@ glm::vec4 UINode::calcColor() const {
|
|||
return color;
|
||||
}
|
||||
|
||||
void UINode::setPos(glm::vec2 pos) {
|
||||
void UINode::setPos(const glm::vec2& pos) {
|
||||
this->pos = pos;
|
||||
}
|
||||
|
||||
|
|
@ -209,7 +215,7 @@ glm::vec2 UINode::getSize() const {
|
|||
return size;
|
||||
}
|
||||
|
||||
void UINode::setSize(glm::vec2 size) {
|
||||
void UINode::setSize(const glm::vec2& size) {
|
||||
this->size = glm::vec2(
|
||||
glm::max(minSize.x, glm::min(maxSize.x, size.x)),
|
||||
glm::max(minSize.y, glm::min(maxSize.y, size.y))
|
||||
|
|
@ -220,7 +226,7 @@ glm::vec2 UINode::getMinSize() const {
|
|||
return minSize;
|
||||
}
|
||||
|
||||
void UINode::setMinSize(glm::vec2 minSize) {
|
||||
void UINode::setMinSize(const glm::vec2& minSize) {
|
||||
this->minSize = minSize;
|
||||
setSize(getSize());
|
||||
}
|
||||
|
|
@ -229,7 +235,7 @@ glm::vec2 UINode::getMaxSize() const {
|
|||
return maxSize;
|
||||
}
|
||||
|
||||
void UINode::setMaxSize(glm::vec2 maxSize) {
|
||||
void UINode::setMaxSize(const glm::vec2& maxSize) {
|
||||
this->maxSize = maxSize;
|
||||
setSize(getSize());
|
||||
}
|
||||
|
|
@ -334,7 +340,7 @@ void UINode::reposition() {
|
|||
}
|
||||
|
||||
void UINode::setGravity(Gravity gravity) {
|
||||
if (gravity == Gravity::none) {
|
||||
if (gravity == Gravity::NONE) {
|
||||
setPositionFunc(nullptr);
|
||||
return;
|
||||
}
|
||||
|
|
@ -349,27 +355,27 @@ void UINode::setGravity(Gravity gravity) {
|
|||
|
||||
float x = 0.0f, y = 0.0f;
|
||||
switch (gravity) {
|
||||
case Gravity::top_left:
|
||||
case Gravity::center_left:
|
||||
case Gravity::bottom_left: x = margin.x; break;
|
||||
case Gravity::top_center:
|
||||
case Gravity::center_center:
|
||||
case Gravity::bottom_center: x = (parentSize.x-size.x)/2.0f; break;
|
||||
case Gravity::top_right:
|
||||
case Gravity::center_right:
|
||||
case Gravity::bottom_right: x = parentSize.x-size.x-margin.z; break;
|
||||
case Gravity::TOP_LEFT:
|
||||
case Gravity::CENTER_LEFT:
|
||||
case Gravity::BOTTOM_LEFT: x = margin.x; break;
|
||||
case Gravity::TOP_CENTER:
|
||||
case Gravity::CENTER_CENTER:
|
||||
case Gravity::BOTTOM_CENTER: x = (parentSize.x-size.x)/2.0f; break;
|
||||
case Gravity::TOP_RIGHT:
|
||||
case Gravity::CENTER_RIGHT:
|
||||
case Gravity::BOTTOM_RIGHT: x = parentSize.x-size.x-margin.z; break;
|
||||
default: break;
|
||||
}
|
||||
switch (gravity) {
|
||||
case Gravity::top_left:
|
||||
case Gravity::top_center:
|
||||
case Gravity::top_right: y = margin.y; break;
|
||||
case Gravity::center_left:
|
||||
case Gravity::center_center:
|
||||
case Gravity::center_right: y = (parentSize.y-size.y)/2.0f; break;
|
||||
case Gravity::bottom_left:
|
||||
case Gravity::bottom_center:
|
||||
case Gravity::bottom_right: y = parentSize.y-size.y-margin.w; break;
|
||||
case Gravity::TOP_LEFT:
|
||||
case Gravity::TOP_CENTER:
|
||||
case Gravity::TOP_RIGHT: y = margin.y; break;
|
||||
case Gravity::CENTER_LEFT:
|
||||
case Gravity::CENTER_CENTER:
|
||||
case Gravity::CENTER_RIGHT: y = (parentSize.y-size.y)/2.0f; break;
|
||||
case Gravity::BOTTOM_LEFT:
|
||||
case Gravity::BOTTOM_CENTER:
|
||||
case Gravity::BOTTOM_RIGHT: y = parentSize.y-size.y-margin.w; break;
|
||||
default: break;
|
||||
}
|
||||
return glm::vec2(x, y);
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ namespace gui {
|
|||
class GUI;
|
||||
class Container;
|
||||
|
||||
using onaction = std::function<void(GUI&)>;
|
||||
using onnumberchange = std::function<void(GUI&, double)>;
|
||||
using onstringchange = std::function<void(GUI&, const std::string&)>;
|
||||
using OnAction = std::function<void(GUI&)>;
|
||||
using OnNumberChange = std::function<void(GUI&, double)>;
|
||||
using OnStringChange = std::function<void(GUI&, const std::string&)>;
|
||||
|
||||
template<typename... Args>
|
||||
class CallbacksSet {
|
||||
|
|
@ -46,28 +46,60 @@ namespace gui {
|
|||
}
|
||||
};
|
||||
|
||||
using ActionsSet = CallbacksSet<GUI&>;
|
||||
template<class TagT, typename... Args>
|
||||
class TaggedCallbacksSet {
|
||||
public:
|
||||
using Func = std::function<void(Args...)>;
|
||||
private:
|
||||
std::unique_ptr<std::vector<std::pair<TagT, Func>>> callbacks;
|
||||
public:
|
||||
void listen(TagT tag, Func&& callback) {
|
||||
if (callbacks == nullptr) {
|
||||
callbacks =
|
||||
std::make_unique<std::vector<std::pair<TagT, Func>>>();
|
||||
}
|
||||
callbacks->push_back({tag, std::move(callback)});
|
||||
}
|
||||
|
||||
void notify(TagT notifyTag, Args&&... args) {
|
||||
if (callbacks == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto& [tag, callback] : * callbacks) {
|
||||
if (tag != notifyTag) {
|
||||
continue;
|
||||
}
|
||||
callback(args...);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum class UIAction {
|
||||
CLICK, DOUBLE_CLICK, FOCUS, DEFOCUS, RIGHT_CLICK
|
||||
};
|
||||
|
||||
using ActionsSet = TaggedCallbacksSet<UIAction, GUI&>;
|
||||
using StringCallbacksSet = CallbacksSet<GUI&, const std::string&>;
|
||||
|
||||
enum class Align {
|
||||
left, center, right,
|
||||
top=left, bottom=right,
|
||||
LEFT, CENTER, RIGHT,
|
||||
TOP=LEFT, BOTTOM=RIGHT,
|
||||
};
|
||||
|
||||
enum class Gravity {
|
||||
none,
|
||||
NONE,
|
||||
|
||||
top_left,
|
||||
top_center,
|
||||
top_right,
|
||||
TOP_LEFT,
|
||||
TOP_CENTER,
|
||||
TOP_RIGHT,
|
||||
|
||||
center_left,
|
||||
center_center,
|
||||
center_right,
|
||||
CENTER_LEFT,
|
||||
CENTER_CENTER,
|
||||
CENTER_RIGHT,
|
||||
|
||||
bottom_left,
|
||||
bottom_center,
|
||||
bottom_right
|
||||
BOTTOM_LEFT,
|
||||
BOTTOM_CENTER,
|
||||
BOTTOM_RIGHT
|
||||
};
|
||||
|
||||
/// @brief Base abstract class for all UI elements
|
||||
|
|
@ -112,21 +144,15 @@ namespace gui {
|
|||
/// @brief z-index property specifies the stack order of an element
|
||||
int zindex = 0;
|
||||
/// @brief element content alignment (supported by Label only)
|
||||
Align align = Align::left;
|
||||
Align align = Align::LEFT;
|
||||
/// @brief parent element
|
||||
UINode* parent = nullptr;
|
||||
/// @brief position supplier for the element (called on parent element size update)
|
||||
vec2supplier positionfunc = nullptr;
|
||||
/// @brief size supplier for the element (called on parent element size update)
|
||||
vec2supplier sizefunc = nullptr;
|
||||
/// @brief 'onclick' callbacks
|
||||
/// @brief parameterless callbacks
|
||||
ActionsSet actions;
|
||||
/// @brief 'ondoubleclick' callbacks
|
||||
ActionsSet doubleClickCallbacks;
|
||||
/// @brief 'onfocus' callbacks
|
||||
ActionsSet focusCallbacks;
|
||||
/// @brief 'ondefocus' callbacks
|
||||
ActionsSet defocusCallbacks;
|
||||
/// @brief element tooltip text
|
||||
std::wstring tooltip;
|
||||
/// @brief element tooltip delay
|
||||
|
|
@ -187,16 +213,17 @@ namespace gui {
|
|||
/// @brief Get element z-index
|
||||
int getZIndex() const;
|
||||
|
||||
virtual UINode* listenAction(const onaction& action);
|
||||
virtual UINode* listenDoubleClick(const onaction& action);
|
||||
virtual UINode* listenFocus(const onaction& action);
|
||||
virtual UINode* listenDefocus(const onaction& action);
|
||||
virtual void listenClick(OnAction action);
|
||||
virtual void listenRightClick(OnAction action);
|
||||
virtual void listenDoubleClick(OnAction action);
|
||||
virtual void listenFocus(OnAction action);
|
||||
virtual void listenDefocus(OnAction action);
|
||||
|
||||
virtual void defocus();
|
||||
virtual void onFocus();
|
||||
virtual void doubleClick(int x, int y);
|
||||
virtual void click(int x, int y);
|
||||
virtual void clicked(Mousecode button) {}
|
||||
virtual void clicked(Mousecode button);
|
||||
virtual void mouseMove(int x, int y) {};
|
||||
virtual void mouseRelease(int x, int y);
|
||||
virtual void scrolled(int value);
|
||||
|
|
@ -240,17 +267,17 @@ namespace gui {
|
|||
virtual glm::vec4 calcColor() const;
|
||||
|
||||
/// @brief Get inner content offset. Used for scroll
|
||||
virtual glm::vec2 getContentOffset() {return glm::vec2(0.0f);};
|
||||
virtual glm::vec2 getContentOffset() const {return glm::vec2(0.0f);};
|
||||
/// @brief Calculate screen position of the element
|
||||
virtual glm::vec2 calcPos() const;
|
||||
virtual void setPos(glm::vec2 pos);
|
||||
virtual void setPos(const glm::vec2& pos);
|
||||
virtual glm::vec2 getPos() const;
|
||||
glm::vec2 getSize() const;
|
||||
virtual void setSize(glm::vec2 size);
|
||||
virtual void setSize(const glm::vec2& size);
|
||||
glm::vec2 getMinSize() const;
|
||||
virtual void setMinSize(glm::vec2 size);
|
||||
virtual void setMinSize(const glm::vec2& size);
|
||||
glm::vec2 getMaxSize() const;
|
||||
virtual void setMaxSize(glm::vec2 size);
|
||||
virtual void setMaxSize(const glm::vec2& size);
|
||||
/// @brief Called in containers when new element added
|
||||
virtual void refresh() {};
|
||||
virtual void fullRefresh() {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
#include <functional>
|
||||
|
||||
namespace gui {
|
||||
enum class Orientation { vertical, horizontal };
|
||||
enum class Orientation { VERTICAL, HORIZONTAL };
|
||||
|
||||
using ontimeout = std::function<void()>;
|
||||
using OnTimeOut = std::function<void()>;
|
||||
|
||||
struct IntervalEvent {
|
||||
ontimeout callback;
|
||||
OnTimeOut callback;
|
||||
float interval;
|
||||
float timer;
|
||||
// -1 - infinity, 1 - one time event
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ void guiutil::confirm(
|
|||
gui, glm::vec2(600, 200), glm::vec4(8.0f), 8.0f
|
||||
);
|
||||
|
||||
panel->setGravity(Gravity::center_center);
|
||||
panel->setGravity(Gravity::CENTER_CENTER);
|
||||
container->add(panel);
|
||||
panel->setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.5f));
|
||||
|
||||
|
|
|
|||
|
|
@ -30,31 +30,31 @@
|
|||
using namespace gui;
|
||||
|
||||
static Align align_from_string(std::string_view str, Align def) {
|
||||
if (str == "left") return Align::left;
|
||||
if (str == "center") return Align::center;
|
||||
if (str == "right") return Align::right;
|
||||
if (str == "top") return Align::top;
|
||||
if (str == "bottom") return Align::bottom;
|
||||
if (str == "left") return Align::LEFT;
|
||||
if (str == "center") return Align::CENTER;
|
||||
if (str == "right") return Align::RIGHT;
|
||||
if (str == "top") return Align::TOP;
|
||||
if (str == "bottom") return Align::BOTTOM;
|
||||
return def;
|
||||
}
|
||||
|
||||
static Gravity gravity_from_string(const std::string& str) {
|
||||
static const std::unordered_map<std::string, Gravity> gravity_names {
|
||||
{"top-left", Gravity::top_left},
|
||||
{"top-center", Gravity::top_center},
|
||||
{"top-right", Gravity::top_right},
|
||||
{"center-left", Gravity::center_left},
|
||||
{"center-center", Gravity::center_center},
|
||||
{"center-right", Gravity::center_right},
|
||||
{"bottom-left", Gravity::bottom_left},
|
||||
{"bottom-center", Gravity::bottom_center},
|
||||
{"bottom-right", Gravity::bottom_right},
|
||||
{"top-left", Gravity::TOP_LEFT},
|
||||
{"top-center", Gravity::TOP_CENTER},
|
||||
{"top-right", Gravity::TOP_RIGHT},
|
||||
{"center-left", Gravity::CENTER_LEFT},
|
||||
{"center-center", Gravity::CENTER_CENTER},
|
||||
{"center-right", Gravity::CENTER_RIGHT},
|
||||
{"bottom-left", Gravity::BOTTOM_LEFT},
|
||||
{"bottom-center", Gravity::BOTTOM_CENTER},
|
||||
{"bottom-right", Gravity::BOTTOM_RIGHT},
|
||||
};
|
||||
auto found = gravity_names.find(str);
|
||||
if (found != gravity_names.end()) {
|
||||
return found->second;
|
||||
}
|
||||
return Gravity::none;
|
||||
return Gravity::NONE;
|
||||
}
|
||||
|
||||
static runnable create_runnable(
|
||||
|
|
@ -73,7 +73,7 @@ static runnable create_runnable(
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
static onaction create_action(
|
||||
static OnAction create_action(
|
||||
const UiXmlReader& reader,
|
||||
const xml::xmlelement& element,
|
||||
const std::string& name
|
||||
|
|
@ -178,7 +178,11 @@ static void read_uinode(
|
|||
}
|
||||
|
||||
if (auto onclick = create_action(reader, element, "onclick")) {
|
||||
node.listenAction(onclick);
|
||||
node.listenClick(onclick);
|
||||
}
|
||||
|
||||
if (auto onclick = create_action(reader, element, "onrightclick")) {
|
||||
node.listenRightClick(onclick);
|
||||
}
|
||||
|
||||
if (auto onfocus = create_action(reader, element, "onfocus")) {
|
||||
|
|
@ -248,7 +252,7 @@ static void read_base_panel_impl(
|
|||
if (element.has("orientation")) {
|
||||
auto& oname = element.attr("orientation").getText();
|
||||
if (oname == "horizontal") {
|
||||
panel.setOrientation(Orientation::horizontal);
|
||||
panel.setOrientation(Orientation::HORIZONTAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -273,7 +277,7 @@ static void read_panel_impl(
|
|||
if (element.has("orientation")) {
|
||||
auto& oname = element.attr("orientation").getText();
|
||||
if (oname == "horizontal") {
|
||||
panel.setOrientation(Orientation::horizontal);
|
||||
panel.setOrientation(Orientation::HORIZONTAL);
|
||||
}
|
||||
}
|
||||
if (subnodes) {
|
||||
|
|
@ -334,7 +338,7 @@ static std::shared_ptr<UINode> read_label(
|
|||
if (element.has("multiline")) {
|
||||
label->setMultiline(element.attr("multiline").asBool());
|
||||
if (!element.has("valign")) {
|
||||
label->setVerticalAlign(Align::top);
|
||||
label->setVerticalAlign(Align::TOP);
|
||||
}
|
||||
}
|
||||
if (element.has("text-wrap")) {
|
||||
|
|
@ -360,8 +364,8 @@ static std::shared_ptr<UINode> read_split_box(
|
|||
float splitPos = element.attr("split-pos", "0.5").asFloat();
|
||||
Orientation orientation =
|
||||
element.attr("orientation", "vertical").getText() == "horizontal"
|
||||
? Orientation::horizontal
|
||||
: Orientation::vertical;
|
||||
? Orientation::HORIZONTAL
|
||||
: Orientation::VERTICAL;
|
||||
auto splitBox = std::make_shared<SplitBox>(
|
||||
reader.getGUI(), glm::vec2(), splitPos, orientation
|
||||
);
|
||||
|
|
@ -569,6 +573,11 @@ static std::shared_ptr<UINode> read_text_box(
|
|||
if (element.has("line-numbers")) {
|
||||
textbox->setShowLineNumbers(element.attr("line-numbers").asBool());
|
||||
}
|
||||
if (element.has("keep-line-selection")) {
|
||||
textbox->setKeepLineSelection(
|
||||
element.attr("keep-line-selection").asBool()
|
||||
);
|
||||
}
|
||||
if (element.has("markup")) {
|
||||
textbox->setMarkup(element.attr("markup").getText());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <streambuf>
|
||||
#include <memory>
|
||||
|
|
|
|||
220
src/io/devices/MemoryDevice.cpp
Normal file
220
src/io/devices/MemoryDevice.cpp
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
#include "MemoryDevice.hpp"
|
||||
|
||||
#include "../memory_istream.hpp"
|
||||
#include "../memory_ostream.hpp"
|
||||
#include "../finalizing_ostream.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
io::MemoryDevice::MemoryDevice() {}
|
||||
|
||||
std::filesystem::path io::MemoryDevice::resolve(std::string_view path) {
|
||||
throw std::runtime_error("unable to resolve filesystem path");
|
||||
}
|
||||
|
||||
std::unique_ptr<std::ostream> io::MemoryDevice::write(std::string_view path) {
|
||||
std::string filePath = std::string(path);
|
||||
return std::make_unique<finalizing_ostream>(
|
||||
std::make_unique<memory_ostream>(),
|
||||
[this, filePath](auto ostream) {
|
||||
auto& memoryStream = dynamic_cast<memory_ostream&>(*ostream);
|
||||
createFile(std::move(filePath), memoryStream.release());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::istream> io::MemoryDevice::read(std::string_view path) {
|
||||
const auto& found = nodes.find(std::string(path));
|
||||
if (found == nodes.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto& node = found->second;
|
||||
if (auto file = node.get_if<File>()) {
|
||||
if (file->content != nullptr) {
|
||||
return std::make_unique<memory_view_istream>(file->content);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t io::MemoryDevice::size(std::string_view path) {
|
||||
const auto& found = nodes.find(std::string(path));
|
||||
if (found == nodes.end()) {
|
||||
return 0;
|
||||
}
|
||||
return std::visit([](auto&& arg) -> size_t {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, File>) {
|
||||
return arg.content.size();
|
||||
} else if constexpr (std::is_same_v<T, Dir>) {
|
||||
return arg.content.size();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}, found->second.data);
|
||||
}
|
||||
|
||||
io::file_time_type io::MemoryDevice::lastWriteTime(std::string_view path) {
|
||||
return file_time_type::min();
|
||||
}
|
||||
|
||||
bool io::MemoryDevice::exists(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return true;
|
||||
}
|
||||
return nodes.find(std::string(path)) != nodes.end();
|
||||
}
|
||||
|
||||
bool io::MemoryDevice::isdir(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return true;
|
||||
}
|
||||
const auto& found = nodes.find(std::string(path));
|
||||
if (found == nodes.end()) {
|
||||
return false;
|
||||
}
|
||||
return found->second.holds_alternative<Dir>();
|
||||
}
|
||||
|
||||
bool io::MemoryDevice::isfile(std::string_view path) {
|
||||
const auto& found = nodes.find(std::string(path));
|
||||
if (found == nodes.end()) {
|
||||
return false;
|
||||
}
|
||||
return found->second.holds_alternative<File>();
|
||||
}
|
||||
|
||||
bool io::MemoryDevice::mkdir(std::string_view path) {
|
||||
return createDir(std::string(path)) != nullptr;
|
||||
}
|
||||
|
||||
bool io::MemoryDevice::mkdirs(std::string_view path) {
|
||||
io::path dirPath = std::string(path);
|
||||
std::vector<std::string> parts;
|
||||
while (!dirPath.pathPart().empty()) {
|
||||
parts.push_back(dirPath.name());
|
||||
dirPath = dirPath.parent();
|
||||
}
|
||||
for (int i = parts.size() - 1; i >= 0; i--) {
|
||||
dirPath = dirPath / parts[i];
|
||||
createDir(dirPath.string());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool io::MemoryDevice::remove(std::string_view path) {
|
||||
std::string pathString = std::string(path);
|
||||
const auto& found = nodes.find(pathString);
|
||||
if (found == nodes.end()) {
|
||||
return false;
|
||||
}
|
||||
if (found->second.holds_alternative<Dir>()) {
|
||||
const auto& dir = found->second.get_if<Dir>();
|
||||
if (!dir->content.empty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
io::path filePath = pathString;
|
||||
io::path parentPath = filePath.parent();
|
||||
auto parentDir = getDir(parentPath.string());
|
||||
if (parentDir) {
|
||||
auto& content = parentDir->content;
|
||||
content.erase(
|
||||
std::remove(content.begin(), content.end(), filePath.name()),
|
||||
content.end()
|
||||
);
|
||||
}
|
||||
nodes.erase(found);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t io::MemoryDevice::removeAll(std::string_view path) {
|
||||
std::string pathString = std::string(path);
|
||||
const auto& found = nodes.find(pathString);
|
||||
if (found == nodes.end()) {
|
||||
return 0;
|
||||
}
|
||||
io::path filePath = pathString;
|
||||
|
||||
uint64_t count = 0;
|
||||
if (found->second.holds_alternative<Dir>()) {
|
||||
auto dir = found->second.get_if<Dir>();
|
||||
auto files = dir->content;
|
||||
for (const auto& name : files) {
|
||||
io::path subPath = filePath / name;
|
||||
count += removeAll(subPath.string());
|
||||
}
|
||||
}
|
||||
if (remove(pathString)) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct MemoryPathsGenerator : public io::PathsGenerator {
|
||||
std::vector<std::string> entries;
|
||||
size_t index = 0;
|
||||
|
||||
MemoryPathsGenerator(std::vector<std::string>&& entries)
|
||||
: entries(std::move(entries)) {}
|
||||
|
||||
bool next(io::path& outPath) override {
|
||||
if (index >= entries.size()) {
|
||||
return false;
|
||||
}
|
||||
outPath = entries[index++];
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<io::PathsGenerator> io::MemoryDevice::list(std::string_view path) {
|
||||
auto dir = getDir(path);
|
||||
if (!dir) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<MemoryPathsGenerator>(
|
||||
std::vector<std::string>(dir->content)
|
||||
);
|
||||
}
|
||||
|
||||
io::MemoryDevice::Dir* io::MemoryDevice::createDir(std::string path) {
|
||||
io::path filePath = path;
|
||||
io::path parent = filePath.parent();
|
||||
auto parentDir = getDir(parent.string());
|
||||
if (!parentDir) {
|
||||
return nullptr;
|
||||
}
|
||||
parentDir->content.push_back(filePath.name());
|
||||
auto& node = nodes[std::move(path)];
|
||||
node.data = Dir {};
|
||||
return node.get_if<Dir>();
|
||||
}
|
||||
|
||||
io::MemoryDevice::Node* io::MemoryDevice::createFile(
|
||||
std::string path, util::Buffer<char>&& content
|
||||
) {
|
||||
io::path filePath = path;
|
||||
io::path parent = filePath.parent();
|
||||
auto dir = getDir(parent.string());
|
||||
if (!dir) {
|
||||
return nullptr;
|
||||
}
|
||||
dir->content.push_back(filePath.name());
|
||||
auto& node = nodes[std::move(path)];
|
||||
node.data = File {std::move(content)};
|
||||
return &node;
|
||||
}
|
||||
|
||||
io::MemoryDevice::Dir* io::MemoryDevice::getDir(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return &rootDir;
|
||||
}
|
||||
const auto& found = nodes.find(std::string(path));
|
||||
if (found == nodes.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto& node = found->second;
|
||||
return node.get_if<Dir>();
|
||||
}
|
||||
71
src/io/devices/MemoryDevice.hpp
Normal file
71
src/io/devices/MemoryDevice.hpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include "Device.hpp"
|
||||
#include "util/Buffer.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace io {
|
||||
/// @brief In-memory filesystem device
|
||||
class MemoryDevice : public Device {
|
||||
enum class NodeType {
|
||||
DIR, FILE
|
||||
};
|
||||
|
||||
struct File {
|
||||
util::Buffer<char> content;
|
||||
};
|
||||
|
||||
struct Dir {
|
||||
std::vector<std::string> content;
|
||||
};
|
||||
|
||||
struct Node {
|
||||
std::variant<Dir, File> data;
|
||||
|
||||
NodeType type() const {
|
||||
return std::visit([](auto&& arg) -> NodeType {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, Dir>) return NodeType::DIR;
|
||||
else if constexpr (std::is_same_v<T, File>) return NodeType::FILE;
|
||||
}, data);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool holds_alternative() const {
|
||||
return std::holds_alternative<T>(data);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* get_if() {
|
||||
return std::get_if<T>(&data);
|
||||
}
|
||||
};
|
||||
public:
|
||||
MemoryDevice();
|
||||
|
||||
std::filesystem::path resolve(std::string_view path) override;
|
||||
std::unique_ptr<std::ostream> write(std::string_view path) override;
|
||||
std::unique_ptr<std::istream> read(std::string_view path) override;
|
||||
size_t size(std::string_view path) override;
|
||||
file_time_type lastWriteTime(std::string_view path) override;
|
||||
bool exists(std::string_view path) override;
|
||||
bool isdir(std::string_view path) override;
|
||||
bool isfile(std::string_view path) override;
|
||||
bool mkdir(std::string_view path) override;
|
||||
bool mkdirs(std::string_view path) override;
|
||||
bool remove(std::string_view path) override;
|
||||
uint64_t removeAll(std::string_view path) override;
|
||||
std::unique_ptr<PathsGenerator> list(std::string_view path) override;
|
||||
private:
|
||||
std::unordered_map<std::string, Node> nodes;
|
||||
Dir rootDir {};
|
||||
|
||||
Node* createFile(std::string path, util::Buffer<char>&& content);
|
||||
Dir* createDir(std::string path);
|
||||
Dir* getDir(std::string_view path);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "Device.hpp"
|
||||
|
||||
namespace io {
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ std::unique_ptr<std::istream> ZipFileDevice::read(std::string_view path) {
|
|||
size_t ZipFileDevice::size(std::string_view path) {
|
||||
const auto& found = entries.find(std::string(path));
|
||||
if (found == entries.end()) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
return found->second.uncompressedSize;
|
||||
}
|
||||
|
|
|
|||
47
src/io/finalizing_ostream.hpp
Normal file
47
src/io/finalizing_ostream.hpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <functional>
|
||||
|
||||
class finalizing_ostream final : public std::ostream {
|
||||
public:
|
||||
finalizing_ostream(
|
||||
std::unique_ptr<std::ostream> inner,
|
||||
std::function<void(std::unique_ptr<std::ostream>)> on_destruction
|
||||
)
|
||||
: std::ostream(inner->rdbuf()),
|
||||
innerStream(std::move(inner)),
|
||||
onDestruction(on_destruction) {
|
||||
}
|
||||
|
||||
finalizing_ostream(const finalizing_ostream&) = delete;
|
||||
finalizing_ostream& operator=(const finalizing_ostream&) = delete;
|
||||
|
||||
finalizing_ostream(finalizing_ostream&& other) noexcept
|
||||
: std::ostream(std::move(other)),
|
||||
innerStream(std::move(other.innerStream)),
|
||||
onDestruction(std::move(other.onDestruction)) {
|
||||
other.onDestruction = nullptr;
|
||||
}
|
||||
|
||||
finalizing_ostream& operator=(finalizing_ostream&& other) noexcept {
|
||||
if (this != &other) {
|
||||
std::ostream::operator=(std::move(other));
|
||||
innerStream = std::move(other.innerStream);
|
||||
onDestruction = std::move(other.onDestruction);
|
||||
other.onDestruction = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~finalizing_ostream() {
|
||||
if (onDestruction) {
|
||||
onDestruction(std::move(innerStream));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<std::ostream> innerStream;
|
||||
std::function<void(std::unique_ptr<std::ostream>)> onDestruction;
|
||||
};
|
||||
|
|
@ -32,3 +32,33 @@ public:
|
|||
private:
|
||||
memory_streambuf buf;
|
||||
};
|
||||
|
||||
class memory_view_streambuf : public std::streambuf {
|
||||
public:
|
||||
explicit memory_view_streambuf(const util::Buffer<char>& buffer)
|
||||
: buffer(std::move(buffer)) {
|
||||
char* base = const_cast<char*>(this->buffer.data());
|
||||
char* end = base + this->buffer.size();
|
||||
setg(base, base, end);
|
||||
}
|
||||
|
||||
memory_view_streambuf(const memory_view_streambuf&) = delete;
|
||||
memory_view_streambuf& operator=(const memory_view_streambuf&) = delete;
|
||||
|
||||
protected:
|
||||
int_type underflow() override {
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
private:
|
||||
const util::Buffer<char>& buffer;
|
||||
};
|
||||
|
||||
class memory_view_istream : public std::istream {
|
||||
public:
|
||||
explicit memory_view_istream(const util::Buffer<char>& buffer)
|
||||
: std::istream(&buf), buf(buffer) {}
|
||||
|
||||
private:
|
||||
memory_view_streambuf buf;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <streambuf>
|
||||
#include <cstring>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
|
|||
builder.add("framerate", &settings.display.framerate);
|
||||
builder.add("limit-fps-iconified", &settings.display.limitFpsIconified);
|
||||
builder.add("window-mode", &settings.display.windowMode);
|
||||
builder.add("adaptive-menu-fps", &settings.display.adaptiveFpsInMenu);
|
||||
|
||||
builder.section("camera");
|
||||
builder.add("sensitivity", &settings.camera.sensitivity);
|
||||
|
|
@ -78,6 +79,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
|
|||
builder.add("ssao", &settings.graphics.ssao);
|
||||
builder.add("shadows-quality", &settings.graphics.shadowsQuality);
|
||||
builder.add("dense-render-distance", &settings.graphics.denseRenderDistance);
|
||||
builder.add("soft-lighting", &settings.graphics.softLighting);
|
||||
|
||||
builder.section("ui");
|
||||
builder.add("language", &settings.ui.language);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include "voxels/Chunks.hpp"
|
||||
#include "voxels/GlobalChunks.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/LevelEvents.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "world/generator/WorldGenerator.hpp"
|
||||
|
||||
|
|
@ -172,13 +173,12 @@ void ChunksController::createChunk(const Player& player, int x, int z) const {
|
|||
auto chunk = level.chunks->create(x, z);
|
||||
player.chunks->putChunk(chunk);
|
||||
auto& chunkFlags = chunk->flags;
|
||||
|
||||
if (!chunkFlags.loaded) {
|
||||
generator->generate(chunk->voxels, x, z);
|
||||
chunkFlags.unsaved = true;
|
||||
}
|
||||
chunk->updateHeights();
|
||||
|
||||
level.events->trigger(LevelEventType::CHUNK_PRESENT, chunk.get());
|
||||
if (!chunkFlags.loadedLights) {
|
||||
Lighting::prebuildSkyLight(*chunk, *level.content.getIndices());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,28 +4,29 @@
|
|||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "engine/Engine.hpp"
|
||||
#include "coders/commons.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "coders/json.hpp"
|
||||
#include "content/ContentReport.hpp"
|
||||
#include "content/ContentControl.hpp"
|
||||
#include "content/ContentReport.hpp"
|
||||
#include "content/PacksManager.hpp"
|
||||
#include "world/files/WorldConverter.hpp"
|
||||
#include "world/files/WorldFiles.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "frontend/locale.hpp"
|
||||
#include "frontend/menu.hpp"
|
||||
#include "frontend/screens/LevelScreen.hpp"
|
||||
#include "frontend/screens/MenuScreen.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "graphics/ui/elements/Menu.hpp"
|
||||
#include "graphics/ui/gui_util.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "interfaces/Task.hpp"
|
||||
#include "LevelController.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "world/files/WorldConverter.hpp"
|
||||
#include "world/files/WorldFiles.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "LevelController.hpp"
|
||||
|
||||
static debug::Logger logger("engine-control");
|
||||
|
||||
|
|
@ -365,7 +366,9 @@ void EngineController::reconfigPacks(
|
|||
);
|
||||
}
|
||||
} else {
|
||||
auto world = controller->getLevel()->getWorld();
|
||||
auto level = controller->getLevel();
|
||||
auto world = level->getWorld();
|
||||
controller->processBeforeQuit();
|
||||
controller->saveWorld();
|
||||
|
||||
auto names = PacksManager::getNames(world->getPacks());
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ LevelController::LevelController(
|
|||
: settings(engine->getSettings()),
|
||||
level(std::move(levelPtr)),
|
||||
chunks(std::make_unique<ChunksController>(*level)),
|
||||
playerTickClock(20, 3) {
|
||||
playerTickClock(20, 3),
|
||||
localPlayer(clientPlayer) {
|
||||
|
||||
level->events->listen(LevelEventType::CHUNK_PRESENT, [](auto, Chunk* chunk) {
|
||||
scripting::on_chunk_present(*chunk, chunk->flags.loaded);
|
||||
|
|
@ -121,6 +122,16 @@ void LevelController::update(float delta, bool pause) {
|
|||
level->entities->clean();
|
||||
}
|
||||
|
||||
void LevelController::processBeforeQuit() {
|
||||
// todo: move somewhere else
|
||||
for (auto player : level->players->getAll()) {
|
||||
if (player->chunks) {
|
||||
player->chunks->saveAndClear();
|
||||
}
|
||||
}
|
||||
scripting::process_before_quit();
|
||||
}
|
||||
|
||||
void LevelController::saveWorld() {
|
||||
auto world = level->getWorld();
|
||||
if (world->isNameless()) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class LevelController {
|
|||
std::unique_ptr<ChunksController> chunks;
|
||||
|
||||
util::Clock playerTickClock;
|
||||
Player* localPlayer;
|
||||
public:
|
||||
LevelController(Engine* engine, std::unique_ptr<Level> level, Player* clientPlayer);
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ public:
|
|||
/// @param pause is world and player simulation paused
|
||||
void update(float delta, bool pause);
|
||||
|
||||
void processBeforeQuit();
|
||||
void saveWorld();
|
||||
|
||||
void onWorldQuit();
|
||||
|
|
|
|||
|
|
@ -207,8 +207,7 @@ void CameraControl::update(
|
|||
tpCamera->front = camera->front;
|
||||
tpCamera->right = camera->right;
|
||||
}
|
||||
if (player.currentCamera == spCamera || player.currentCamera == tpCamera ||
|
||||
player.currentCamera == camera) {
|
||||
if (player.isCurrentCameraBuiltin()) {
|
||||
player.currentCamera->setFov(glm::radians(settings.fov.get()));
|
||||
}
|
||||
}
|
||||
|
|
@ -280,7 +279,7 @@ void PlayerController::postUpdate(
|
|||
updateFootsteps(delta);
|
||||
}
|
||||
|
||||
if (!pause && input) {
|
||||
if (!pause && input && player.isCurrentCameraBuiltin()) {
|
||||
camControl.updateMouse(this->input, windowHeight);
|
||||
}
|
||||
camControl.refreshRotation();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,93 @@
|
|||
#include "api_lua.hpp"
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "io/devices/MemoryDevice.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "content/ContentControl.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "util/platform.hpp"
|
||||
#include "window/Window.hpp"
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
static int l_start_debug_instance(lua::State* L) {
|
||||
int port = lua::tointeger(L, 1);
|
||||
if (port == 0) {
|
||||
port = engine->getNetwork().findFreePort();
|
||||
if (port == -1) {
|
||||
throw std::runtime_error("could not find free port");
|
||||
}
|
||||
}
|
||||
const auto& paths = engine->getPaths();
|
||||
|
||||
std::vector<std::string> args {
|
||||
"--res", paths.getResourcesFolder().u8string(),
|
||||
"--dir", paths.getUserFilesFolder().u8string(),
|
||||
"--dbg-server", "tcp:" + std::to_string(port),
|
||||
};
|
||||
platform::new_engine_instance(std::move(args));
|
||||
return lua::pushinteger(L, port);
|
||||
}
|
||||
|
||||
static int l_focus(lua::State* L) {
|
||||
engine->getWindow().focus();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_create_memory_device(lua::State* L) {
|
||||
std::string name = lua::require_string(L, 1);
|
||||
if (io::get_device(name)) {
|
||||
throw std::runtime_error(
|
||||
"entry-point '" + name + "' is already used"
|
||||
);
|
||||
}
|
||||
if (name.find(':') != std::string::npos) {
|
||||
throw std::runtime_error("invalid entry point name");
|
||||
}
|
||||
|
||||
io::set_device(name, std::make_unique<io::MemoryDevice>());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_content_sources(lua::State* L) {
|
||||
const auto& sources = engine->getContentControl().getContentSources();
|
||||
lua::createtable(L, static_cast<int>(sources.size()), 0);
|
||||
for (size_t i = 0; i < sources.size(); i++) {
|
||||
lua::pushlstring(L, sources[i].string());
|
||||
lua::rawseti(L, static_cast<int>(i + 1));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_set_content_sources(lua::State* L) {
|
||||
if (!lua::istable(L, 1)) {
|
||||
throw std::runtime_error("table expected as argument 1");
|
||||
}
|
||||
int len = lua::objlen(L, 1);
|
||||
std::vector<io::path> sources;
|
||||
for (int i = 0; i < len; i++) {
|
||||
lua::rawgeti(L, i + 1);
|
||||
sources.emplace_back(std::string(lua::require_lstring(L, -1)));
|
||||
lua::pop(L);
|
||||
}
|
||||
engine->getContentControl().setContentSources(std::move(sources));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_reset_content_sources(lua::State* L) {
|
||||
engine->getContentControl().resetContentSources();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg applib[] = {
|
||||
// see libcore.cpp an stdlib.lua
|
||||
{"start_debug_instance", lua::wrap<l_start_debug_instance>},
|
||||
{"focus", lua::wrap<l_focus>},
|
||||
{"create_memory_device", lua::wrap<l_create_memory_device>},
|
||||
{"get_content_sources", lua::wrap<l_get_content_sources>},
|
||||
{"set_content_sources", lua::wrap<l_set_content_sources>},
|
||||
{"reset_content_sources", lua::wrap<l_reset_content_sources>},
|
||||
// for other functions see libcore.cpp and stdlib.lua
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
#include "engine/Engine.hpp"
|
||||
#include "graphics/commons/Model.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "graphics/core/Atlas.hpp"
|
||||
#include "util/Buffer.hpp"
|
||||
#include "../usertypes/lua_type_canvas.hpp"
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
|
|
@ -64,8 +66,47 @@ static int l_parse_model(lua::State* L) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int l_to_canvas(lua::State* L) {
|
||||
auto& assets = *engine->getAssets();
|
||||
|
||||
auto alias = lua::require_lstring(L, 1);
|
||||
size_t sep = alias.rfind(':');
|
||||
if (sep == std::string::npos) {
|
||||
auto texture = assets.getShared<Texture>(std::string(alias));
|
||||
if (texture != nullptr) {
|
||||
auto image = texture->readData();
|
||||
return lua::newuserdata<lua::LuaCanvas>(
|
||||
L, texture, std::move(image)
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
auto atlasName = alias.substr(0, sep);
|
||||
|
||||
if (auto atlas = assets.get<Atlas>(std::string(atlasName))) {
|
||||
auto textureName = std::string(alias.substr(sep + 1));
|
||||
auto image = atlas->shareImageData();
|
||||
auto texture = atlas->shareTexture();
|
||||
|
||||
if (auto region = atlas->getIf(textureName)) {
|
||||
UVRegion uvRegion = *region;
|
||||
int atlasWidth = static_cast<int>(image->getWidth());
|
||||
int atlasHeight = static_cast<int>(image->getHeight());
|
||||
int x = static_cast<int>(uvRegion.u1 * atlasWidth);
|
||||
int y = static_cast<int>(uvRegion.v1 * atlasHeight);
|
||||
int w = static_cast<int>(uvRegion.getWidth() * atlasWidth);
|
||||
int h = static_cast<int>(uvRegion.getHeight() * atlasHeight);
|
||||
return lua::newuserdata<lua::LuaCanvas>(
|
||||
L, std::move(texture), image->cropped(x, y, w, h), uvRegion
|
||||
);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg assetslib[] = {
|
||||
{"load_texture", lua::wrap<l_load_texture>},
|
||||
{"parse_model", lua::wrap<l_parse_model>},
|
||||
{"to_canvas", lua::wrap<l_to_canvas>},
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "audio/audio.hpp"
|
||||
#include "assets/Assets.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "api_lua.hpp"
|
||||
|
||||
inline const char* DEFAULT_CHANNEL = "regular";
|
||||
|
|
@ -67,6 +68,26 @@ inline audio::speakerid_t play_stream(
|
|||
if (channel == -1) {
|
||||
return 0;
|
||||
}
|
||||
if (!scripting::engine->isHeadless()) {
|
||||
auto assets = scripting::engine->getAssets();
|
||||
|
||||
auto stream = assets->getShared<audio::PCMStream>(filename);
|
||||
if (stream) {
|
||||
return audio::play(
|
||||
audio::open_stream(std::move(stream), true),
|
||||
glm::vec3(
|
||||
static_cast<float>(x),
|
||||
static_cast<float>(y),
|
||||
static_cast<float>(z)
|
||||
),
|
||||
relative,
|
||||
volume,
|
||||
pitch,
|
||||
loop,
|
||||
channel
|
||||
);
|
||||
}
|
||||
}
|
||||
io::path file;
|
||||
if (std::strchr(filename, ':')) {
|
||||
file = std::string(filename);
|
||||
|
|
@ -359,16 +380,80 @@ static int l_audio_get_velocity(lua::State* L) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// @brief audio.count_speakers() -> integer
|
||||
/// @brief audio.count_speakers() -> integer
|
||||
static int l_audio_count_speakers(lua::State* L) {
|
||||
return lua::pushinteger(L, audio::count_speakers());
|
||||
}
|
||||
|
||||
// @brief audio.count_streams() -> integer
|
||||
/// @brief audio.count_streams() -> integer
|
||||
static int l_audio_count_streams(lua::State* L) {
|
||||
return lua::pushinteger(L, audio::count_streams());
|
||||
}
|
||||
|
||||
/// @brief audio.input.fetch(size) -> Bytearray
|
||||
static int l_audio_fetch_input(lua::State* L) {
|
||||
auto device = audio::get_input_device();
|
||||
if (device == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
size_t size = lua::touinteger(L, 1);
|
||||
const size_t MAX_BUFFER_SIZE = audio::MAX_INPUT_SAMPLES * 4;
|
||||
if (size == 0) {
|
||||
size = MAX_BUFFER_SIZE;
|
||||
}
|
||||
size = std::min<size_t>(size, MAX_BUFFER_SIZE);
|
||||
ubyte buffer[MAX_BUFFER_SIZE];
|
||||
size = device->read(reinterpret_cast<char*>(buffer), size);
|
||||
|
||||
std::vector<ubyte> bytes(buffer, buffer + size);
|
||||
return lua::create_bytearray(L, std::move(bytes));
|
||||
}
|
||||
|
||||
static int l_audio_get_input_devices_names(lua::State* L) {
|
||||
auto device_names = audio::get_input_devices_names();
|
||||
lua::createtable(L, device_names.size(), 0);
|
||||
int index = 1;
|
||||
for (const auto& name : device_names) {
|
||||
lua::pushstring(L, name.c_str());
|
||||
lua::rawseti(L, index++);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_audio_get_output_devices_names(lua::State* L) {
|
||||
auto device_names = audio::get_output_devices_names();
|
||||
lua::createtable(L, device_names.size(), 0);
|
||||
int index = 1;
|
||||
for (const auto& name : device_names) {
|
||||
lua::pushstring(L, name.c_str());
|
||||
lua::rawseti(L, index++);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_audio_set_input_device(lua::State* L) {
|
||||
auto device_name = lua::tostring(L, 1);
|
||||
audio::set_input_device(device_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_audio_get_input_info(lua::State* L) {
|
||||
auto device = audio::get_input_device();
|
||||
if (device == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
lua::createtable(L, 0, 4);
|
||||
lua::pushlstring(L, device->getDeviceSpecifier());
|
||||
lua::setfield(L, "device_specifier");
|
||||
lua::pushinteger(L, device->getChannels());
|
||||
lua::setfield(L, "channels");
|
||||
lua::pushinteger(L, device->getSampleRate());
|
||||
lua::setfield(L, "sample_rate");
|
||||
lua::pushinteger(L, device->getBitsPerSample());
|
||||
lua::setfield(L, "bits_per_sample");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const luaL_Reg audiolib[] = {
|
||||
{"play_sound", lua::wrap<l_audio_play_sound>},
|
||||
{"play_sound_2d", lua::wrap<l_audio_play_sound_2d>},
|
||||
|
|
@ -394,5 +479,10 @@ const luaL_Reg audiolib[] = {
|
|||
{"get_velocity", lua::wrap<l_audio_get_velocity>},
|
||||
{"count_speakers", lua::wrap<l_audio_count_speakers>},
|
||||
{"count_streams", lua::wrap<l_audio_count_streams>},
|
||||
{"__fetch_input", lua::wrap<l_audio_fetch_input>},
|
||||
{"get_input_devices_names", lua::wrap<l_audio_get_input_devices_names>},
|
||||
{"get_output_devices_names", lua::wrap<l_audio_get_output_devices_names>},
|
||||
{"set_input_device", lua::wrap<l_audio_set_input_device>},
|
||||
{"get_input_info", lua::wrap<l_audio_get_input_info>},
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#include "coders/binary_json.hpp"
|
||||
#include "api_lua.hpp"
|
||||
#include "util/Buffer.hpp"
|
||||
#include "../lua_custom_types.hpp"
|
||||
|
||||
static int l_tobytes(lua::State* L) {
|
||||
auto value = lua::tovalue(L, 1);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue