rename engine to src

This commit is contained in:
2018-09-16 14:21:58 +02:00
parent eccb34724e
commit 71de44a7c1
120 changed files with 282 additions and 282 deletions

58
src/action.cpp Normal file
View File

@@ -0,0 +1,58 @@
#include "pch.h"
#include "log.h"
#include "action.h"
#include "app.h"
ActionManager ActionManager::I;
void ActionManager::add(Action *action)
{
I.m_redos = std::stack<std::unique_ptr<Action>>();
I.m_actions.emplace(action);
I.m_memory += action->memory();
//LOG("History: %.2f KB", I.m_memory / 1024.f);
App::I.update_memory_usage(I.m_memory);
}
void ActionManager::undo()
{
if (I.m_actions.empty())
return;
I.m_redos.emplace(I.m_actions.top()->get_redo());
I.m_redos.top()->was_saved = !ui::Canvas::I->m_unsaved;
I.m_actions.top()->undo();
I.m_memory -= I.m_actions.top()->memory();
ui::Canvas::I->m_unsaved = !I.m_actions.top()->was_saved;
I.m_actions.pop();
//LOG("History: %.2f KB", I.m_memory / 1024.f);
App::I.update_memory_usage(I.m_memory);
App::I.title_update();
}
void ActionManager::redo()
{
if (I.m_redos.empty())
return;
I.m_actions.emplace(I.m_redos.top()->get_redo());
I.m_actions.top()->was_saved = !ui::Canvas::I->m_unsaved;
I.m_memory += I.m_actions.top()->memory();
I.m_redos.top()->undo();
ui::Canvas::I->m_unsaved = !I.m_redos.top()->was_saved;
I.m_redos.pop();
//LOG("History: %.2f KB", I.m_memory / 1024.f);
App::I.update_memory_usage(I.m_memory);
App::I.title_update();
}
void ActionManager::clear()
{
while (!I.m_actions.empty())
I.m_actions.pop();
I.m_memory = 0;
//LOG("History: %.2f KB", I.m_memory / 1024.f);
App::I.update_memory_usage(I.m_memory);
}

29
src/action.h Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
class Action
{
public:
bool was_saved = false;
virtual void run() = 0;
virtual void undo() = 0;
virtual Action* get_redo() = 0;
virtual size_t memory() = 0;
virtual ~Action(){};
};
class ActionManager
{
public:
static ActionManager I;
std::stack<std::unique_ptr<Action>> m_actions;
std::stack<std::unique_ptr<Action>> m_redos;
size_t m_memory = 0;
static void add(Action* action);
static void undo();
static void redo();
static void clear();
static bool empty()
{
return I.m_actions.empty();
}
};

764
src/app.cpp Normal file
View File

@@ -0,0 +1,764 @@
#include "pch.h"
#include "log.h"
#include "app.h"
#include "node_icon.h"
#include "node_dialog_open.h"
#include "node_progress_bar.h"
#ifdef __APPLE__
#include <Foundation/Foundation.h>
#endif
#ifdef __ANDROID__
void android_async_lock(struct engine* engine);
void android_async_swap(struct engine* engine);
void android_async_unlock(struct engine* engine);
#elif _WIN32
bool async_lock_try();
void async_lock();
void async_swap();
void async_unlock();
void destroy_window();
#endif
using namespace ui;
App App::I; // singleton
void App::create()
{
width = 1920/2;
height = 1080/2;
}
bool App::request_close()
{
static bool dialog_already_opened = false;
if (!ui::Canvas::I->m_unsaved)
return true;
if (!dialog_already_opened)
{
async_start();
auto* m = layout[main_id]->add_child<NodeMessageBox>();
m->m_title->set_text("Unsaved document");
m->m_message->set_text("Do you want to close without saving?");
m->btn_ok->m_text->set_text("Yes");
m->btn_ok->on_click = [this](Node*) {
#ifdef _WIN32
destroy_window();
PostQuitMessage(0);
#endif
#ifdef __OSX__
[osx_view close];
#endif
ui::Canvas::I->m_unsaved = false;
};
m->btn_cancel->m_text->set_text("No");
m->btn_cancel->on_click = [this,m](Node*) {
m->destroy();
dialog_already_opened = false;
};
async_redraw();
async_end();
dialog_already_opened = true;
}
return false;
}
void App::clear()
{
glClearColor(.1f, .1f, .1f, 1.f);
glViewport(0, 0, (GLsizei)width, (GLsizei)height);
glClear(GL_COLOR_BUFFER_BIT);
}
void App::initAssets()
{
LOG("initializing assets");
FontManager::init();
LOG("initializing assets loading fonts");
FontManager::load(kFont::Arial_11, "data/arial.ttf", 15);
FontManager::load(kFont::Arial_30, "data/arial.ttf", 30);
LOG("initializing assets create sampler");
sampler.create(GL_NEAREST);
LOG("initializing assets load uvs texture");
if (!tex.load("data/uvs.jpg"))
LOG("error loading image");
LOG("initializing assets completed");
}
void App::initLog()
{
#if defined(__IOS__) || defined(__OSX__)
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* docpath = [paths objectAtIndex:0];
data_path = [docpath cStringUsingEncoding:NSASCIIStringEncoding];
NSString* recpath = [docpath stringByAppendingString:@"/rec"];
rec_path = [recpath cStringUsingEncoding:NSASCIIStringEncoding];
NSError* recerr = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:recpath withIntermediateDirectories:YES attributes:nil error:&recerr])
{
LOG("error creating rec path: %s", [[recerr localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
}
#elif _WIN32
//CHAR my_documents[MAX_PATH];
//HRESULT result = SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, my_documents);
//HMODULE hModule = GetModuleHandle(NULL);
//CHAR path[MAX_PATH];
//GetModuleFileNameA(hModule, path, MAX_PATH);
//CHAR out_drive[MAX_PATH];
//CHAR out_path[MAX_PATH];
//_splitpath(path, out_drive, out_path, nullptr, nullptr);
//sprintf_s(path, "%s%s", out_drive, out_path);
//data_path = path;
CHAR path[MAX_PATH];
GetCurrentDirectoryA(sizeof(path), path);
data_path = path;
rec_path = data_path + "\\frames";
#endif
//LogRemote::I.start();
LogRemote::I.file_init();
}
int progress_callback_download(void *clientp, curl_off_t dltotal,
curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
std::function<void(float)> progress = *(std::function<void(float)>*)clientp;
progress((float)dlnow / (float)dltotal);
return 0;
}
int progress_callback_upload(void *clientp, curl_off_t dltotal,
curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
std::function<void(float)> progress = *(std::function<void(float)>*)clientp;
progress((float)ulnow / (float)ultotal);
return 0;
}
void App::download(std::string filename, std::function<void(float)> progress)
{
CURL *curl = curl_easy_init();
if (curl)
{
auto dest = data_path + "/" + filename;
FILE* fp = fopen(dest.c_str(), "wb");
std::string url = "http://omigamedev.com/panopainter/cloud/cloud-dwl.php?file=" + filename;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_write);
if (progress)
{
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback_download);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
}
auto err = curl_easy_perform(curl);
curl_easy_cleanup(curl);
fclose(fp);
}
}
bool App::check_license()
{
CURL *curl = curl_easy_init();
if (curl)
{
std::string url = "http://omigamedev.com/panopainter/79516B99-8E67-40AD-B12F-149A5A9C2E15";
//std::string url = "https://panopainter.com/license/E8EDC2FE-E1BD-4AB1-91BD-FCCD926739BD.php"; // wacom
//std::string url = "https://panopainter.com/license/A744FBA9-BC6C-43C8-BD24-0CCE24B3D985.php"; // others
std::string ret;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
//curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2L);
auto err = curl_easy_perform(curl);
curl_easy_cleanup(curl);
LOG("License check: %s", ret.c_str());
if (err == CURLcode::CURLE_OK)
return true;
}
return false;
}
void App::upload(std::string filename, std::string name, std::function<void(float)> progress)
{
CURL *curl;
struct curl_httppost *formpost = NULL;
struct curl_httppost *lastptr = NULL;
//curl_global_init(CURL_GLOBAL_ALL);
curl_formadd(&formpost,
&lastptr,
CURLFORM_COPYNAME, "fileToUpload",
CURLFORM_FILE, filename.c_str(),
CURLFORM_END);
curl = curl_easy_init();
std::string res;
if (curl)
{
std::string url = "http://omigamedev.com/panopainter/cloud/cloud-upl.php?name=" + name;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &res);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler);
if (progress)
{
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback_upload);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
}
auto err = curl_easy_perform(curl);
std::cout << "\n\nUPLOAD RESULT\n" << res << "\n\n\n";
curl_easy_cleanup(curl);
}
}
void App::init()
{
#ifdef _WIN32
static CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
// colors: http://stackoverflow.com/questions/4053837/colorizing-text-in-the-console-with-c
glDebugMessageCallback([](GLenum source, GLenum type, GLuint id,
GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
{
static std::map<GLenum, int> colors = {
{ GL_DEBUG_SEVERITY_NOTIFICATION, 8 },
{ GL_DEBUG_SEVERITY_LOW, 8 },
{ GL_DEBUG_SEVERITY_MEDIUM, FOREGROUND_GREEN | FOREGROUND_INTENSITY },
{ GL_DEBUG_SEVERITY_HIGH, FOREGROUND_RED | FOREGROUND_INTENSITY },
};
if (severity == GL_DEBUG_SEVERITY_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM || severity == GL_DEBUG_SEVERITY_LOW)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colors[severity]);
LOG("OPENGL: %.*s", length, message);
FlushConsoleInputBuffer(GetStdHandle(STD_OUTPUT_HANDLE));
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), info.wAttributes);
//__debugbreak();
}
}, nullptr);
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
#endif
LOG("GL version: %s", glGetString(GL_VERSION));
LOG("GL vendor: %s", glGetString(GL_VENDOR));
LOG("GL renderer: %s", glGetString(GL_RENDERER));
// GLint n_exts;
// glGetIntegerv(GL_NUM_EXTENSIONS, &n_exts);
// for (int i = 0; i < n_exts; i++)
// {
// LOG("%s", glGetStringi(GL_EXTENSIONS, i));
// }
LOG("Screen Resolution: %dx%d", (int)width, (int)height);
//zoom = ceilf(width / 2000.f);
//zoom = 2;
glDisable(GL_DEPTH_TEST);
#if defined(_WIN32) || defined(__OSX__)
glEnable(GL_PROGRAM_POINT_SIZE);
glEnable(GL_LINE_SMOOTH);
#endif
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_ADD);
initShaders();
initAssets();
initLayout();
title_update();
GLfloat width_range[2];
glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, width_range);
LOG("GL line range: %f - %f", width_range[0], width_range[1]);
LOG("Screen Size: %f %f", width, height);
GLint fb0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fb0);
LOG("Default Framebuffer %d", fb0);
}
void App::async_start()
{
#if __OSX__
[osx_view async_lock];
#elif __IOS__
[ios_view async_lock];
#elif __ANDROID__
android_async_lock(and_engine);
#elif _WIN32
async_lock();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
#endif
}
void App::async_update()
{
#if __IOS__
[ios_view->glview bindDrawable];
#elif _WIN32
glBindFramebuffer(GL_FRAMEBUFFER, 0);
#endif
redraw = true;
clear();
update(0);
#if __OSX__
[osx_view async_swap];
#elif __IOS__
[ios_view->glview bindDrawable];
[ios_view async_swap];
#elif __ANDROID__
android_async_swap(and_engine);
#elif _WIN32
async_swap();
#endif
}
void App::async_redraw()
{
redraw = true;
}
void App::async_end()
{
#if __OSX__
[osx_view async_unlock];
#elif __IOS__
[ios_view async_unlock];
#elif __ANDROID__
android_async_unlock(and_engine);
#elif _WIN32
async_unlock();
#endif
}
void App::update(float dt)
{
static float rec_timer = 0.f;
static std::mutex m;
std::lock_guard<std::mutex> _lock(m);
// update offscreen stuff
if (canvas && canvas->m_canvas)
canvas->m_canvas->stroke_draw();
if (!(redraw || animate))
return;
//glClearColor(.1f, .1f, .1f, 1.f);
//glViewport(0, 0, (GLsizei)width, (GLsizei)height);
//glClear(GL_COLOR_BUFFER_BIT);
//if (!canvas->m_mouse_captured)
{
#if _WIN32 || __OSX__
layout.reload();
#endif
if (auto* main = layout[main_id])
{
main->update(width, height, zoom);
stroke->update_controls();
}
}
static glm::vec4 color_button_normal{.1, .1, .1, 1};
static glm::vec4 color_button_hlight{ 1, .0, .0, 1};
CanvasModePen* mode = (CanvasModePen*)canvas->m_canvas->modes[(int)Canvas::kCanvasMode::Draw][0];
layout[main_id]->find<NodeButton>("btn-pick")->set_color(
mode->m_picking ? color_button_hlight : color_button_normal);
layout[main_id]->find<NodeButton>("btn-pen")->set_color(
canvas->m_canvas->m_current_mode == Canvas::kCanvasMode::Draw ? color_button_hlight : color_button_normal);
layout[main_id]->find<NodeButton>("btn-erase")->set_color(
canvas->m_canvas->m_current_mode == Canvas::kCanvasMode::Erase ? color_button_hlight : color_button_normal);
layout[main_id]->find<NodeButton>("btn-touchlock")->set_color(
canvas->m_canvas->m_touch_lock ? color_button_hlight : color_button_normal);
auto observer = [this](Node* n)
{
if (n && n->m_display)
{
auto box = n->m_clip;
//glm::ivec4 c = glm::vec4((int)box.x - 1, (int)(height / zoom - box.y - box.w) - 1, (int)box.z + 2, (int)box.w + 2) * zoom;
glm::ivec4 c = glm::vec4((int)box.x, (int)(height / zoom - box.y - box.w), (int)box.z, (int)box.w) * zoom;
glScissor(c.x, c.y, c.z, c.w);
n->draw();
return true;
}
return false;
};
//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#if __IOS__
[ios_view->glview bindDrawable];
#else
glBindFramebuffer(GL_FRAMEBUFFER, 0);
#endif
glEnable(GL_SCISSOR_TEST);
if (auto* main = layout[main_id])
main->watch(observer);
//msgbox->watch(observer);
glDisable(GL_SCISSOR_TEST);
if (rec_running)
{
rec_timer += dt;
if (rec_timer > 1.f && canvas->m_canvas->m_dirty_stroke)
{
canvas->m_canvas->m_dirty_stroke = false;
LOG("rec tick");
rec_timer = 0.f;
auto data = new uint8_t[width * height * 4];
#if __IOS__
[ios_view->glview bindDrawable];
#else
glBindFramebuffer(GL_FRAMEBUFFER, 0);
#endif
GLint dfbo, rfbo;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &rfbo);
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &dfbo);
if (dfbo != rfbo)
LOG("DIFFERENT FB");
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
{
std::lock_guard<std::mutex> lock(rec_mutex);
rec_frames.emplace_back(data);
rec_cv.notify_all();
}
update_rec_frames();
}
}
redraw = false;
}
void App::terminate()
{
LOG("App::terminate");
TextureManager::invalidate();
ShaderManager::invalidate();
layout.clear_context();
brushes->clear_context();
layers->clear_context();
color->clear_context();
stroke->clear_context();
rec_stop();
}
void App::update_memory_usage(size_t bytes)
{
if (auto txt = layout[main_id]->find<NodeText>("txt-memory"))
{
static char buffer[128];
sprintf(buffer, "History memory: %.2f Mb", bytes / 1024.f / 1024.f);
txt->set_text(buffer);
layout[main_id]->update();
}
}
void App::update_rec_frames()
{
if (auto txt = layout[main_id]->find<NodeText>("txt-rec"))
{
if (rec_running)
{
static char buffer[128];
sprintf(buffer, "Recorder %d frames", rec_count);
txt->set_text(buffer);
}
else
{
txt->set_text("");
}
layout[main_id]->update();
}
}
void App::rec_clear()
{
rec_stop();
#if defined(__IOS__) || defined(__OSX__)
NSString *path = [NSString stringWithUTF8String:rec_path.c_str()];
NSDirectoryEnumerator* en = [[NSFileManager defaultManager] enumeratorAtPath:path];
NSError* err = nil;
BOOL res;
NSString* file;
while (file = [en nextObject])
{
NSString* file_path = [path stringByAppendingPathComponent:file];
[[NSFileManager defaultManager] removeItemAtPath:file_path error:nil];
NSLog(@"delete: %@", file_path);
}
#endif
rec_count = 0;
update_rec_frames();
}
void App::rec_start()
{
if (!rec_running)
{
#if defined(__IOS__) || defined(__OSX__)
NSString* path = [NSString stringWithUTF8String:rec_path.c_str()];
NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
NSArray *jpgFiles = [dirFiles filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"self ENDSWITH '.jpg'"]];
rec_count = (int)[jpgFiles count];
update_rec_frames();
rec_thread = std::thread(&App::rec_loop, this);
#else
rec_count = Asset::list_files(rec_path, false, ".*\\.jpg").size();
update_rec_frames();
rec_thread = std::thread(&App::rec_loop, this);
#endif
}
}
void App::rec_stop()
{
if (rec_running)
{
rec_running = false;
rec_cv.notify_all();
if (rec_thread.joinable())
rec_thread.join();
update_rec_frames();
}
}
void stillImageDataReleaseCallback(void *releaseRefCon, const void *baseAddress)
{
free((void *)baseAddress);
}
void App::rec_export(std::string path)
{
int progress = 0;
int tot = rec_count;
auto pb = layout[main_id]->add_child<NodeProgressBar>();
pb->m_progress->SetWidthP(0);
pb->m_title->set_text("Exporting MP4 movie");
async_update();
#if defined(__IOS__) || defined(__OSX__)
NSString* mov_path = [NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()];
if ([[NSFileManager defaultManager] fileExistsAtPath:mov_path])
{
NSLog(@"remove existing mp4");
[[NSFileManager defaultManager] removeItemAtPath:mov_path error:nil];
}
NSURL* url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()]];
AVAssetWriter* writer = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeMPEG4 error:nil];
writer.shouldOptimizeForNetworkUse = NO;
NSDictionary *videoCompressionSettings = @{
AVVideoCodecKey : AVVideoCodecH264,
AVVideoWidthKey : @(width),
AVVideoHeightKey : @(height),
AVVideoCompressionPropertiesKey : @{ AVVideoAverageBitRateKey : @(8<<20) }
};
if (![writer canApplyOutputSettings:videoCompressionSettings forMediaType:AVMediaTypeVideo])
{
NSLog(@"Couldn't add asset writer video input.");
return;
}
AVAssetWriterInput* input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:videoCompressionSettings
sourceFormatHint:nil];
input.expectsMediaDataInRealTime = YES;
NSDictionary *adaptorDict = @{
(id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA),
(id)kCVPixelBufferWidthKey : @(width),
(id)kCVPixelBufferHeightKey : @(height)
};
AVAssetWriterInputPixelBufferAdaptor* _pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc]
initWithAssetWriterInput:input
sourcePixelBufferAttributes:adaptorDict];
// Add asset writer input to asset writer
if (![writer canAddInput:input]) {
NSLog(@"Couldn't add input to writer.");
return;
}
[writer addInput:input];
CMTime t;
t.timescale = 30;
t.flags = kCMTimeFlags_Valid;
t.epoch = 0;
t.value = 0;
//[writer startSessionAtSourceTime:t];
CVPixelBufferRef buff = NULL;
uint8_t* data = (uint8_t*)calloc(1, width * height * 4);
CVPixelBufferCreateWithBytes(kCFAllocatorDefault, width, height, kCVPixelFormatType_32RGBA, data,
width * 4, stillImageDataReleaseCallback, nil, nil, &buff);
OSStatus err = CVPixelBufferPoolCreatePixelBuffer(nil, _pixelBufferAdaptor.pixelBufferPool, &buff);
if (writer.status == AVAssetWriterStatusUnknown)
{
// If the asset writer status is unknown, implies writing hasn't started yet, hence start writing with start time as the buffer's presentation timestamp
if ([writer startWriting])
{
[writer startSessionAtSourceTime:t];
}
}
if (writer.status == AVAssetWriterStatusWriting)
{
for (int i = 0; i < tot; i++)
{
// If the asset writer status is writing, append sample buffer to its corresponding asset writer input
if (input.readyForMoreMediaData)
{
char path[256];
snprintf(path, sizeof(path), "%s/%04d.jpg", rec_path.c_str(), i);
NSString* img_path = [NSString stringWithUTF8String:path];
NSLog(@"frame: %@", img_path);
#if __OSX__
NSImage *image = [[NSImage alloc] initWithContentsOfFile:img_path];
if (!image)
break;
NSRect imageRect = NSMakeRect(0, 0, image.size.width, image.size.height);
CGImageRef cgImage = [image CGImageForProposedRect:&imageRect context:NULL hints:nil];
#elif __IOS__
UIImage* image = [UIImage imageNamed:img_path];
if (!image)
break;
CGImageRef cgImage = image.CGImage;
#endif
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
[NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, nil];
CGSize sz = image.size;
CVPixelBufferRef pxbuffer = NULL;
{
CGImageRef image = cgImage;
CGSize size = sz;
CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options, &pxbuffer);
// CVReturn status = CVPixelBufferPoolCreatePixelBuffer(NULL, adaptor.pixelBufferPool, &pxbuffer);
//NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);
CVPixelBufferLockBaseAddress(pxbuffer, 0);
void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
//NSParameterAssert(pxdata != NULL);
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pxdata, size.width, size.height, 8, 4*size.width, rgbColorSpace, kCGImageAlphaPremultipliedFirst);
//NSParameterAssert(context);
CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);
CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
}
t.value = i;
if (![_pixelBufferAdaptor appendPixelBuffer:pxbuffer withPresentationTime:t])
{
NSLog(@"error %@", [writer.error localizedFailureReason]);
}
CFRelease(pxbuffer);
progress++;
pb->m_progress->SetWidthP((float)progress / tot * 100.f);
async_update();
}
}
[input markAsFinished];
[writer finishWritingWithCompletionHandler:^{
NSString* path = [NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()];
NSLog(@"saved video %@", path);
#if __IOS__
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path))
{
NSLog(@"saving to camera roll");
UISaveVideoAtPathToSavedPhotosAlbum(path, nil, nil, nil);
}
#endif
}];
}
if (writer.status == AVAssetWriterStatusFailed)
{
NSLog(@"failed");
}
#endif
pb->destroy();
async_update();
}
void App::rec_loop()
{
rec_running = true;
while(rec_running)
{
std::unique_lock<std::mutex> lock(rec_mutex);
rec_cv.wait(lock);
if (!rec_running)
break;
if (!rec_frames.empty())
{
if (rec_frames.front())
{
auto inverted = std::make_unique<uint8_t[]>(width*height*4);
for (int y = height - 1, y1 = 0; y >= 0; y--, y1++)
{
uint8_t* dst = &inverted[y * width * 4];
uint8_t* src = &rec_frames.front()[y1 * width * 4];
std::copy_n(src, (int)width * 4, dst);
}
char path[256];
snprintf(path, sizeof(path), "%s/%04d.jpg", rec_path.c_str(), rec_count);
LOG("writing %s", path);
jpge::params params;
params.m_quality = 75;
bool saved = jpge::compress_image_to_jpeg_file(path, width, height, 4, inverted.get(), params);
if (!saved)
{
LOG("error writing the frame");
rec_running = false;
}
else
{
rec_count++;
redraw = true;
}
}
rec_frames.pop_front();
}
}
}

165
src/app.h Normal file
View File

@@ -0,0 +1,165 @@
#pragma once
#include "log.h"
#include "shader.h"
#include "shape.h"
#include "texture.h"
#include "layout.h"
#include "font.h"
#include "node_message_box.h"
#include "node_settings.h"
#include "node_popup_menu.h"
#include "node_panel_brush.h"
#include "node_panel_layer.h"
#include "node_panel_color.h"
#include "node_panel_stroke.h"
#include "node_scroll.h"
#include "node_canvas.h"
#include "node_dialog_layer_rename.h"
#include "node_progress_bar.h"
#if defined(__OBJC__) && defined(__IOS__)
#import <Foundation/Foundation.h>
#import "GameViewController.h"
#endif
#if defined(__OBJC__) && defined(__OSX__)
#import "main.h"
#endif
#ifdef __ANDROID__
#include "main.h"
#endif
#include "node_panel_grid.h"
class App
{
public:
static App I;
std::string data_path{ "." };
std::string rec_path{ "." };
std::thread rec_thread;
bool rec_running = false;
int rec_count = 0;
std::mutex rec_mutex;
std::condition_variable rec_cv;
std::deque<std::unique_ptr<uint8_t[]>> rec_frames;
Sampler sampler;
Texture2D tex;
LayoutManager layout;
NodeMessageBox* msgbox;
NodeSettings* settings;
NodePopupMenu* popup = nullptr;
NodePopupMenu* menu_file = nullptr;
NodePopupMenu* menu_edit = nullptr;
NodePopupMenu* menu_layers = nullptr;
NodeBorder* sidebar = nullptr;
std::shared_ptr<NodePanelBrush> brushes;
std::shared_ptr<NodePanelLayer> layers;
std::shared_ptr<NodePanelColor> color;
std::shared_ptr<NodePanelStroke> stroke;
std::shared_ptr<NodePanelGrid> grid;
std::shared_ptr<NodePanelBrushPreset> presets;
NodeCanvas* canvas;
Node* current_panel = nullptr;
NodeScroll* panels;
const uint16_t main_id = const_hash("main");
std::string doc_name = "no-name";
float width;
float height;
bool keys[256];
bool redraw = true;
bool animate = false;
glm::vec2 gesture_p0;
glm::vec2 gesture_p1;
#ifdef __ANDROID__
float zoom = 3.0;
#elif __IOS__
float zoom = 2.0;
#else
float zoom = 1.0;
#endif // __ANDROID__
#if defined(__IOS__) && defined(__OBJC__)
GameViewController* ios_view;
#endif
#if defined(__OSX__) && defined(__OBJC__)
View* osx_view;
#endif
#ifdef __ANDROID__
struct android_app* and_app;
struct engine* and_engine;
#endif
void pick_image(std::function<void(std::string path)> callback);
void showKeyboard();
void hideKeyboard();
void initLog();
void init();
void initShaders();
void initAssets();
void initLayout();
void create();
bool request_close();
void terminate();
void clear();
void update(float dt);
void async_start();
void async_update();
void async_redraw();
void async_end();
void resize(float w, float h);
bool mouse_down(int button, float x, float y, float pressure, kEventSource source);
bool mouse_move(float x, float y, float pressure, kEventSource source);
bool mouse_up(int button, float x, float y, kEventSource source);
bool mouse_scroll(float x, float y, float delta);
bool mouse_cancel(int button);
bool gesture_start(const glm::vec2& p0, const glm::vec2& p1);
bool gesture_move(const glm::vec2& p0, const glm::vec2& p1);
bool gesture_end();
bool key_down(kKey key);
bool key_up(kKey key);
bool key_char(char key);
void toggle_ui();
void rec_clear();
void rec_loop();
void rec_start();
void rec_stop();
void rec_export(std::string path);
void init_toolbar_main();
void init_toolbar_draw();
void init_sidebar();
void init_menu_file();
void init_menu_edit();
void init_menu_layer();
void init_menu_timelapse();
void dialog_newdoc();
void dialog_save();
void dialog_save_ver();
void dialog_open();
void dialog_browse();
void dialog_export();
void dialog_export_cubes();
void dialog_layer_rename();
void cloud_upload();
void cloud_upload_all();
void cloud_browse();
void upload(std::string filename, std::string name = "", std::function<void(float)> progress = nullptr);
void download(std::string filename, std::function<void(float)> progress = nullptr);
bool check_license();
std::shared_ptr<NodeProgressBar> show_progress(const std::string& title);
void brush_update();
void title_update();
void update_memory_usage(size_t bytes);
void update_rec_frames();
void cmd_convert(std::string pano_path, std::string out_path);
};

161
src/app_cloud.cpp Normal file
View File

@@ -0,0 +1,161 @@
#include "pch.h"
#include "app.h"
#include "util.h"
#include "node_progress_bar.h"
#include "node_dialog_cloud.h"
using namespace ui;
void App::cloud_upload()
{
if (!canvas)
return;
if (ui::Canvas::I->m_newdoc)
{
auto msgbox = new NodeMessageBox();
msgbox->m_manager = &layout;
msgbox->init();
msgbox->m_title->set_text("Warning");
msgbox->m_message->set_text("This document needs to be saved before upload.");
layout[main_id]->add_child(msgbox);
layout[main_id]->update();
}
else
{
std::thread([this] {
std::string path = data_path + "/" + doc_name + ".pano";
if (ui::Canvas::I->m_unsaved)
{
Canvas::I->project_save_thread(path);
}
async_start();
auto pb = show_progress("Uploading");
async_redraw();
async_end();
upload(path, doc_name + ".pano", [this,pb](float p){
async_start();
pb->m_progress->SetWidthP(p * 100.f);
async_redraw();
async_end();
});
async_start();
pb->destroy();
auto msgbox = new NodeMessageBox();
msgbox->m_manager = &layout;
msgbox->init();
msgbox->m_title->set_text("Success");
msgbox->m_message->set_text("This document has been succesfully uploaded.");
layout[main_id]->add_child(msgbox);
layout[main_id]->update();
async_redraw();
async_end();
}).detach();
}
}
void App::cloud_upload_all()
{
std::thread([this] {
auto names = Asset::list_files(data_path, false, ".*\\.pano");
gl_state gl;
std::shared_ptr<NodeProgressBar> pb;
if (layout.m_loaded)
{
async_start();
pb = show_progress("Export Pano Image");
async_redraw();
async_end();
}
int progress = 0;
int total = names.size();
for (const auto& n : names)
{
std::string path = data_path + "/" + n;
upload(path);
progress++;
float p = (float)progress / total * 100.f;
LOG("progress: %f", p);
if (layout.m_loaded)
{
async_start();
pb->m_progress->SetWidthP(p);
gl.save();
async_redraw();
gl.restore();
async_end();
}
}
if (layout.m_loaded)
{
async_start();
pb->destroy();
async_redraw();
async_end();
}
}).detach();
}
void App::cloud_browse()
{
if (!canvas)
return;
// load thumbnail test
auto dialog = std::make_shared<NodeDialogCloud>();
dialog->m_manager = &layout;
dialog->data_path = data_path;
dialog->init();
dialog->create();
dialog->loaded();
layout[main_id]->add_child(dialog);
layout[main_id]->update();
dialog->btn_ok->on_click = [this, dialog](Node*)
{
dialog->destroy();
std::thread([this, dialog] {
async_start();
auto* m = layout[main_id]->add_child<NodeMessageBox>();
m->m_title->set_text("Downloading");
m->m_message->set_text("Download in progress");
async_redraw();
async_end();
download(dialog->selected_file, [this,m](float p){
static char progress[256];
sprintf(progress, "Download in progress %.2f%%", p * 100.f);
async_start();
m->m_message->set_text(progress);
async_redraw();
async_end();
});
async_start();
canvas->reset_camera();
layers->clear();
async_end();
canvas->m_canvas->project_open_thread(dialog->selected_path);
async_start();
doc_name = dialog->selected_name;
title_update();
for (auto& i : canvas->m_canvas->m_order)
layers->add_layer(canvas->m_canvas->m_layers[i].m_name.c_str());
ActionManager::clear();
m->destroy();
async_redraw();
async_end();
}).detach();
};
}

16
src/app_commands.cpp Normal file
View File

@@ -0,0 +1,16 @@
#include "pch.h"
#include "app.h"
#include "canvas.h"
void App::cmd_convert(std::string pano_path, std::string out_path)
{
glDisable(GL_DEPTH_TEST);
glEnable(GL_PROGRAM_POINT_SIZE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_ADD);
ui::Canvas* canvas = new ui::Canvas;
canvas->create(CANVAS_RES, CANVAS_RES);
canvas->project_open_thread(pano_path);
canvas->export_equirectangular_thread(out_path);
}

381
src/app_dialogs.cpp Normal file
View File

@@ -0,0 +1,381 @@
#include "pch.h"
#include "app.h"
#include "node_dialog_open.h"
#include "node_dialog_browse.h"
#include "node_dialog_cloud.h"
std::shared_ptr<NodeProgressBar> App::show_progress(const std::string& title)
{
auto pb = std::make_shared<NodeProgressBar>();
pb->m_manager = &layout;
pb->init();
pb->create();
pb->loaded();
pb->m_progress->SetWidthP(0);
pb->m_title->set_text(title.c_str());
layout[main_id]->add_child(pb);
return pb;
}
void App::dialog_newdoc()
{
auto show_dialog = [this] {
async_start();
auto dialog = std::make_shared<NodeDialogNewDoc>();
dialog->m_manager = &layout;
dialog->init();
dialog->create();
dialog->loaded();
dialog->input->set_text("name");
layout[main_id]->add_child(dialog);
layout[main_id]->update();
App::I.showKeyboard();
dialog->btn_ok->on_click = [this, dialog](Node*)
{
std::string name = dialog->input->m_string;
std::string path = data_path + "/" + name + ".pano";
auto action = [this, dialog, name] {
std::array<int, 4> resolutions{ 512, 1024, 1536, 2048 };
int res = resolutions[dialog->m_resolution->m_current_index];
doc_name = name;
layers->clear();
canvas->m_canvas->m_layers.clear();
canvas->m_canvas->m_order.clear();
canvas->m_canvas->resize(res, res);
canvas->reset_camera();
ActionManager::clear();
canvas->m_canvas->layer_add("Default");
layers->add_layer("Default");
title_update();
dialog->destroy();
App::I.hideKeyboard();
};
if (Asset::exist(path, false))
{
// ask confirm is file already exist
auto msgbox = new NodeMessageBox();
msgbox->m_manager = &layout;
msgbox->init();
msgbox->m_title->set_text("Warning");
msgbox->m_message->set_text("A document with this name already exists, continue?");
msgbox->btn_ok->on_click = [this, msgbox, action](Node*) {
action();
msgbox->destroy();
};
layout[main_id]->add_child(msgbox);
}
else
{
action();
}
};
dialog->btn_cancel->on_click = [this, dialog](Node*)
{
dialog->destroy();
App::I.hideKeyboard();
};
async_end();
};
if (canvas)
{
if (ui::Canvas::I->m_unsaved)
{
auto m = layout[main_id]->add_child<NodeMessageBox>();
m->m_title->set_text("Unsaved document");
m->m_message->set_text("Would you like to save this document before closing?");
m->btn_ok->m_text->set_text("Yes");
m->btn_cancel->m_text->set_text("No");
m->btn_ok->on_click = [this, m, show_dialog](Node*) {
ui::Canvas::I->project_save([this, m, show_dialog] {
show_dialog();
});
m->destroy();
};
m->btn_cancel->on_click = [this, m, show_dialog](Node*) {
show_dialog();
m->destroy();
};
}
else
{
show_dialog();
}
}
}
void App::dialog_open()
{
auto show_dialog = [this] {
async_start();
// load thumbnail test
auto dialog = std::make_shared<NodeDialogOpen>();
dialog->m_manager = &layout;
dialog->data_path = data_path;
dialog->init();
dialog->create();
dialog->loaded();
layout[main_id]->add_child(dialog);
layout[main_id]->update();
dialog->btn_ok->on_click = [this, dialog](Node*)
{
canvas->reset_camera();
layers->clear();
doc_name = dialog->selected_name;
canvas->m_canvas->project_open(dialog->selected_path, [this] {
// on complete
async_start();
title_update();
for (auto& i : canvas->m_canvas->m_order)
layers->add_layer(canvas->m_canvas->m_layers[i].m_name.c_str());
async_end();
});
dialog->destroy();
ActionManager::clear();
};
async_end();
};
if (canvas)
{
if (ui::Canvas::I->m_unsaved)
{
auto m = layout[main_id]->add_child<NodeMessageBox>();
m->m_title->set_text("Unsaved document");
m->m_message->set_text("Would you like to save this document before closing?");
m->btn_ok->m_text->set_text("Yes");
m->btn_cancel->m_text->set_text("No");
m->btn_ok->on_click = [this,m,show_dialog](Node*){
ui::Canvas::I->project_save([this,m,show_dialog] {
show_dialog();
});
m->destroy();
};
m->btn_cancel->on_click = [this,m,show_dialog](Node*) {
show_dialog();
m->destroy();
};
}
else
{
show_dialog();
}
}
}
void App::dialog_browse()
{
auto show_dialog = [this] {
async_start();
// load thumbnail test
auto dialog = std::make_shared<NodeDialogBrowse>();
dialog->m_manager = &layout;
dialog->data_path = data_path;
dialog->init();
dialog->create();
dialog->loaded();
layout[main_id]->add_child(dialog);
layout[main_id]->update();
dialog->btn_ok->on_click = [this, dialog](Node*)
{
canvas->reset_camera();
layers->clear();
doc_name = dialog->selected_name;
canvas->m_canvas->project_open(dialog->selected_path, [this] {
// on complete
async_start();
title_update();
for (auto& i : canvas->m_canvas->m_order)
layers->add_layer(canvas->m_canvas->m_layers[i].m_name.c_str());
async_end();
});
dialog->destroy();
ActionManager::clear();
};
async_end();
};
if (canvas)
{
if (ui::Canvas::I->m_unsaved)
{
auto m = layout[main_id]->add_child<NodeMessageBox>();
m->m_title->set_text("Unsaved document");
m->m_message->set_text("Would you like to save this document before closing?");
m->btn_ok->m_text->set_text("Yes");
m->btn_cancel->m_text->set_text("No");
m->btn_ok->on_click = [this, m, show_dialog](Node*) {
ui::Canvas::I->project_save([this, m, show_dialog] {
show_dialog();
});
m->destroy();
};
m->btn_cancel->on_click = [this, m, show_dialog](Node*) {
show_dialog();
m->destroy();
};
}
else
{
show_dialog();
}
}
}
void App::dialog_save_ver()
{
int current = 0;
std::string next = doc_name + ".01";
std::string base = doc_name;
std::regex r(R"((.*)\.(\w{2})$)");
std::smatch m;
if (std::regex_search(doc_name, m, r))
{
base = m[1].str();
current = atoi(m[2].str().c_str());
}
for (int i = current + 1; i < 99; i++)
{
static char tmp_name[256];
sprintf(tmp_name, "%s.%02d", base.c_str(), i);
next = tmp_name;
if (Asset::exist(data_path + "/" + next + ".pano", false))
continue;
break;
}
doc_name = next;
title_update();
canvas->m_canvas->project_save(data_path + "/" + next + ".pano");
}
void App::dialog_save()
{
if (canvas)
{
auto dialog = std::make_shared<NodeDialogSave>();
dialog->m_manager = &layout;
dialog->init();
dialog->create();
dialog->loaded();
dialog->input->set_text(doc_name);
App::I.showKeyboard();
dialog->btn_ok->on_click = [this, dialog](Node*)
{
std::string name = dialog->input->m_string;
std::string path = data_path + "/" + name + ".pano";
if (name.empty())
{
auto msgbox = new NodeMessageBox();
msgbox->m_manager = &layout;
msgbox->init();
msgbox->m_title->set_text("Warning");
msgbox->m_message->set_text("You need to specify a name to file.");
layout[main_id]->add_child(msgbox);
return;
}
auto action = [this, dialog, name, path] {
doc_name = name;
title_update();
canvas->m_canvas->project_save(path);
dialog->destroy();
App::I.hideKeyboard();
};
if (Asset::exist(path, false))
{
// ask confirm is file already exist
auto msgbox = new NodeMessageBox();
msgbox->m_manager = &layout;
msgbox->init();
msgbox->m_title->set_text("Warning");
msgbox->m_message->set_text(("Are you sure you want to overwrite " + name + "?").c_str());
msgbox->btn_ok->on_click = [this, msgbox, action](Node*) {
action();
msgbox->destroy();
};
layout[main_id]->add_child(msgbox);
}
else
{
action();
}
};
dialog->btn_cancel->on_click = [this, dialog](Node*)
{
dialog->destroy();
App::I.hideKeyboard();
};
layout[main_id]->add_child(dialog);
layout[main_id]->update();
}
}
void App::dialog_export()
{
if (canvas)
{
canvas->m_canvas->export_equirectangular(data_path + "/" + doc_name + ".jpg");
}
}
void App::dialog_export_cubes()
{
if (canvas)
{
canvas->m_canvas->export_cubes(data_path + "/" + doc_name);
}
}
void App::dialog_layer_rename()
{
auto dialog = std::make_shared<NodeDialogLayerRename>();
dialog->m_manager = &layout;
dialog->data_path = data_path;
dialog->init();
dialog->create();
dialog->loaded();
dialog->input->set_text(layers->m_current_layer->m_label_text);
App::I.showKeyboard();
layout[main_id]->add_child(dialog);
layout[main_id]->update();
dialog->btn_ok->on_click = [this,dialog](Node*)
{
layers->m_current_layer->set_name(dialog->get_name().c_str());
canvas->m_canvas->m_layers[canvas->m_canvas->m_current_layer_idx].m_name = dialog->get_name();
dialog->destroy();
App::I.hideKeyboard();
};
dialog->btn_cancel->on_click = [this, dialog](Node*)
{
dialog->destroy();
App::I.hideKeyboard();
};
popup->mouse_release();
popup->destroy();
}

199
src/app_events.cpp Normal file
View File

@@ -0,0 +1,199 @@
#include "pch.h"
#include "app.h"
#ifdef __ANDROID__
void displayKeyboard(android_app* mApplication, bool pShow);
#elif _WIN32
std::string win32_open_file();
#endif
using namespace ui;
void App::resize(float w, float h)
{
redraw = true;
width = w;
height = h;
if (auto* main = layout[main_id])
main->update(w , h, zoom);
}
void App::showKeyboard()
{
LOG("show keyboard");
redraw = true;
#ifdef __IOS__
[ios_view registerForKeyboardNotifications];
[ios_view becomeFirstResponder];
#elif __ANDROID__
displayKeyboard(and_app, true);
#endif
}
void App::hideKeyboard()
{
LOG("hide keyboard");
redraw = true;
#ifdef __IOS__
[ios_view resignFirstResponder];
// [ios_view unregisterForKeyboardNotifications];
#elif __ANDROID__
displayKeyboard(and_app, false);
#endif
}
void App::pick_image(std::function<void(std::string path)> callback)
{
redraw = true;
#ifdef __IOS__
[ios_view pick_photo:callback];
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
std::string path = [osx_view pick_file];
if (!path.empty())
callback(path);
});
#elif __ANDROID__
//displayKeyboard(and_app, false);
#elif _WIN32
std::string path = win32_open_file();
if (!path.empty())
callback(path);
#endif
}
bool App::mouse_down(int button, float x, float y, float pressure, kEventSource source)
{
redraw = true;
MouseEvent e;
e.m_type = button ? kEventType::MouseDownR : kEventType::MouseDownL;
e.m_pos = { x / zoom, y / zoom };
e.m_pressure = pressure;
e.m_source = source;
auto ret = layout[main_id]->on_event(&e);
layout[main_id]->update();
return ret == kEventResult::Consumed;
}
bool App::mouse_move(float x, float y, float pressure, kEventSource source)
{
redraw = true;
MouseEvent e;
e.m_type = kEventType::MouseMove;
e.m_pos = { x / zoom, y / zoom };
e.m_pressure = pressure;
e.m_source = source;
kEventResult ret = kEventResult::Available;
if (auto* main = layout[main_id])
ret = main->on_event(&e);
return ret == kEventResult::Consumed;
}
bool App::mouse_up(int button, float x, float y, kEventSource source)
{
redraw = true;
MouseEvent e;
e.m_type = button ? kEventType::MouseUpR : kEventType::MouseUpL;
e.m_pos = { x / zoom, y / zoom };
e.m_source = source;
auto ret = layout[main_id]->on_event(&e);
layout[main_id]->update();
return ret == kEventResult::Consumed;
}
bool App::mouse_scroll(float x, float y, float delta)
{
redraw = true;
MouseEvent e;
e.m_type = kEventType::MouseScroll;
e.m_pos = { x / zoom, y / zoom };
e.m_scroll_delta = delta;
auto ret = layout[main_id]->on_event(&e);
layout[main_id]->update();
return ret == kEventResult::Consumed;
}
bool App::mouse_cancel(int button)
{
redraw = true;
MouseEvent e;
e.m_type = kEventType::MouseCancel;
auto ret = layout[main_id]->on_event(&e);
layout[main_id]->update();
return ret == kEventResult::Consumed;
}
bool App::gesture_start(const glm::vec2& p0, const glm::vec2& p1)
{
redraw = true;
GestureEvent e;
glm::vec2 p = glm::lerp(p0, p1, 0.5f);
e.m_type = kEventType::GestureStart;
e.m_pos = p / glm::vec2(zoom);
e.m_distance = glm::distance(p0, p1);
gesture_p0 = p0;
gesture_p1 = p1;
auto ret = layout[main_id]->on_event(&e);
layout[main_id]->update();
return ret == kEventResult::Consumed;
}
bool App::gesture_move(const glm::vec2& p0, const glm::vec2& p1)
{
redraw = true;
GestureEvent e;
glm::vec2 p = glm::lerp(p0, p1, 0.5f);
e.m_type = kEventType::GestureMove;
e.m_pos = p / glm::vec2(zoom);
e.m_distance = glm::distance(p0, p1);
e.m_distance_delta = e.m_distance - glm::distance(gesture_p0, gesture_p1);
e.m_pos_delta = p - glm::lerp(gesture_p0, gesture_p1, 0.5f);
auto ret = layout[main_id]->on_event(&e);
layout[main_id]->update();
return ret == kEventResult::Consumed;
}
bool App::gesture_end()
{
redraw = true;
GestureEvent e;
e.m_type = kEventType::GestureEnd;
auto ret = layout[main_id]->on_event(&e);
layout[main_id]->update();
return ret == kEventResult::Consumed;
}
bool App::key_down(kKey key)
{
redraw = true;
keys[(int)key] = true;
KeyEvent e;
e.m_type = kEventType::KeyDown;
e.m_key = key;
auto ret = layout[main_id]->on_event(&e);
layout[main_id]->update();
return ret == kEventResult::Consumed;
}
bool App::key_up(kKey key)
{
redraw = true;
keys[(int)key] = false;
KeyEvent e;
e.m_type = kEventType::KeyUp;
e.m_key = key;
auto ret = layout[main_id]->on_event(&e);
layout[main_id]->update();
return ret == kEventResult::Consumed;
}
bool App::key_char(char key)
{
redraw = true;
KeyEvent e;
e.m_type = kEventType::KeyChar;
e.m_char = key;
auto ret = layout[main_id]->on_event(&e);
layout[main_id]->update();
return ret == kEventResult::Consumed;
}
void App::toggle_ui()
{
static bool fullscreen = false;
auto m = layout[main_id]->m_children[0];
for (int i = 2; i < m->m_children.size(); i++)
m->m_children[i]->m_display = fullscreen;
fullscreen = !fullscreen;
}

743
src/app_layout.cpp Normal file
View File

@@ -0,0 +1,743 @@
#include "pch.h"
#include "app.h"
#include "node_icon.h"
#include "node_dialog_open.h"
#include "node_text.h"
#include "node_progress_bar.h"
#include "node_dialog_picker.h"
using namespace ui;
static glm::vec4 color_button_normal{ .1, .1, .1, 1 };
static glm::vec4 color_button_hlight{ 1, .0, .0, 1 };
void App::title_update()
{
static char str[256];
snprintf(str, 256, "Panodoc: %s%s (%dpx)", doc_name.c_str(), canvas->m_canvas->m_unsaved ? "*" : "", canvas->m_canvas->m_width);
if (auto docname = layout[main_id]->find<NodeText>("txt-docname"))
docname->set_text(str);
}
void App::init_toolbar_main()
{
if (auto* button = layout[main_id]->find<NodeButton>("btn-export"))
{
button->on_click = [this, button](Node*) {
if (canvas)
{
canvas->m_canvas->export_equirectangular(data_path);
}
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-anim"))
{
button->on_click = [this, button](Node*) {
if (canvas)
{
canvas->m_canvas->export_anim(data_path);
}
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-open"))
{
button->on_click = [this, button](Node*) {
dialog_open();
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-save"))
{
button->on_click = [this, button](Node*) {
dialog_save();
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-undo"))
{
button->on_click = [this, button](Node*) {
ActionManager::undo();
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-redo"))
{
button->on_click = [this, button](Node*) {
ActionManager::redo();
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-clean-memory"))
{
button->on_click = [this](Node*) {
ActionManager::clear();
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-clear"))
{
button->on_click = [this](Node*) {
//exit(0);
if (canvas)
canvas->m_canvas->clear({ 0, 0, 0, 0 });
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-popup"))
{
button->on_click = [this](Node*) {
msgbox = new NodeMessageBox();
msgbox->m_manager = &layout;
msgbox->init();
layout[main_id]->add_child(msgbox);
layout[main_id]->update();
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-settings"))
{
button->on_click = [this](Node*) {
settings = new NodeSettings();
settings->m_manager = &layout;
settings->init();
layout[main_id]->add_child(settings);
layout[main_id]->update();
};
}
}
void App::init_sidebar()
{
sidebar = layout[main_id]->find<NodeBorder>("sidebar");
panels = layout[main_id]->find<NodeScroll>("panels");
canvas = layout[main_id]->find<NodeCanvas>("paint-canvas");
canvas->data_path = data_path;
//brushes = layout[main_id]->find<NodePanelBrush>("panel-brush");
//layers = layout[main_id]->find<NodePanelLayer>("panel-layer");
//color = layout[main_id]->find<NodePanelColor>("panel-color");
//stroke = layout[main_id]->find<NodePanelStroke>("panel-stroke");
brushes = std::make_shared<NodePanelBrush>();
brushes->m_manager = &layout;
brushes->init();
brushes->create();
brushes->loaded();
layers = std::make_shared<NodePanelLayer>();
layers->m_manager = &layout;
layers->init();
layers->create();
layers->loaded();
color = std::make_shared<NodePanelColor>();
color->m_manager = &layout;
color->init();
color->create();
color->loaded();
stroke = std::make_shared<NodePanelStroke>();
stroke->m_manager = &layout;
stroke->init();
stroke->create();
stroke->loaded();
grid = std::make_shared<NodePanelGrid>();
grid->m_manager = &layout;
grid->init();
grid->create();
grid->loaded();
presets = std::make_shared<NodePanelBrushPreset>();
presets->m_manager = &layout;
presets->init();
presets->create();
presets->loaded();
// if (canvas)
// {
// ui::Canvas::I->m_current_brush.m_tip_color = color->m_color;
// stroke->m_canvas->draw_stroke();
// }
brushes->on_brush_changed = [this](Node* target, int index) {
ui::Canvas::I->m_current_brush.m_tex_id = brushes->get_texture_id(index);
ui::Canvas::I->m_current_brush.id = brushes->get_brush_id(index);
stroke->m_preview->draw_stroke();
};
presets->on_brush_changed = [this](Node* target, int index) {
auto b = presets->get_brush(index);
// don't change some params
b.m_tip_size = ui::Canvas::I->m_current_brush.m_tip_size;
b.m_tip_color = ui::Canvas::I->m_current_brush.m_tip_color;
ui::Canvas::I->m_current_brush = b;
stroke->m_preview->draw_stroke();
};
color->on_color_changed = [this](Node* target, glm::vec4 color) {
ui::Canvas::I->m_current_brush.m_tip_color = color;
};
//
// stroke->on_stroke_change = [this](Node*target) {
// if (canvas)
// canvas->m_brush = stroke->m_canvas->m_brush;
// };
layers->on_layer_add = [this](Node*) {
canvas->m_canvas->layer_add(layers->m_layers.back()->m_label_text.c_str());
};
layers->on_layer_change = [this](Node*, int old_idx, int new_idx) {
canvas->m_canvas->m_current_layer_idx = canvas->m_canvas->m_order[new_idx];
};
layers->on_layer_order = [this](Node*, int old_idx, int new_idx) {
canvas->m_canvas->layer_order(old_idx, new_idx);
};
layers->on_layer_delete = [this](Node*, int idx) {
canvas->m_canvas->layer_remove(canvas->m_canvas->m_order[idx]);
};
layers->on_layer_opacity_changed = [this](Node*, int idx, float value) {
canvas->m_canvas->m_layers[canvas->m_canvas->m_order[idx]].m_opacity = value;
};
layers->on_layer_visibility_changed = [this](Node*, int idx, bool visible) {
canvas->m_canvas->m_layers[canvas->m_canvas->m_order[idx]].m_alpha_locked = visible;
};
layers->on_layer_highlight_changed = [this](Node*, int idx, bool highlight) {
canvas->m_canvas->m_layers[canvas->m_canvas->m_order[idx]].m_hightlight = highlight;
};
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-stroke"))
{
button->on_click = [this, button](Node*) {
panels->get_child_index(stroke.get()) == -1 ? panels->add_child(stroke) : panels->remove_child(stroke.get());
panels->fix_scroll();
button->set_color(panels->get_child_index(stroke.get()) == -1 ? color_button_normal : color_button_hlight);
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-brush"))
{
button->on_click = [this, button](Node*) {
panels->get_child_index(brushes.get()) == -1 ? panels->add_child(brushes) : panels->remove_child(brushes.get());
panels->fix_scroll();
button->set_color(panels->get_child_index(brushes.get()) == -1 ? color_button_normal : color_button_hlight);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-brush-preset"))
{
button->on_click = [this, button](Node*) {
panels->get_child_index(presets.get()) == -1 ? panels->add_child(presets) : panels->remove_child(presets.get());
panels->fix_scroll();
button->set_color(panels->get_child_index(presets.get()) == -1 ? color_button_normal : color_button_hlight);
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-color"))
{
button->on_click = [this, button](Node*) {
panels->get_child_index(color.get()) == -1 ? panels->add_child(color) : panels->remove_child(color.get());
panels->fix_scroll();
button->set_color(panels->get_child_index(color.get()) == -1 ? color_button_normal : color_button_hlight);
// auto pick = layout[main_id]->add_child<NodeColorPicker>();
// pick->m_color_cur->m_color = ui::Canvas::I->m_current_brush.m_tip_color;
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-layer"))
{
button->on_click = [this, button](Node*) {
panels->get_child_index(layers.get()) == -1 ? panels->add_child(layers) : panels->remove_child(layers.get());
panels->fix_scroll();
button->set_color(panels->get_child_index(layers.get()) == -1 ? color_button_normal : color_button_hlight);
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-grids-panel"))
{
button->on_click = [this, button](Node*) {
panels->get_child_index(grid.get()) == -1 ? panels->add_child(grid) : panels->remove_child(grid.get());
panels->fix_scroll();
button->set_color(panels->get_child_index(grid.get()) == -1 ? color_button_normal : color_button_hlight);
};
}
}
void App::init_toolbar_draw()
{
static auto select_button = [] (Node* main, NodeButton* button) {
main->find<NodeButton>("btn-pen")->set_color(color_button_normal);
main->find<NodeButton>("btn-erase")->set_color(color_button_normal);
main->find<NodeButton>("btn-line")->set_color(color_button_normal);
main->find<NodeButton>("btn-cam")->set_color(color_button_normal);
main->find<NodeButton>("btn-grid")->set_color(color_button_normal);
//main->find<NodeButton>("btn-fill")->set_color(color_button_normal);
main->find<NodeButton>("btn-mask-free")->set_color(color_button_normal);
main->find<NodeButton>("btn-mask-line")->set_color(color_button_normal);
button->set_color(color_button_hlight);
};
if (auto* button = layout[main_id]->find<NodeButton>("btn-pen"))
{
button->on_click = [this, button](Node*) {
select_button(layout[main_id], button);
Canvas::set_mode(Canvas::kCanvasMode::Draw);
};
layout[main_id]->find<NodeButton>("btn-pen")->set_color(color_button_hlight);
Canvas::set_mode(Canvas::kCanvasMode::Draw);
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-pick"))
{
button->on_click = [this](Node*) {
CanvasModePen* mode = (CanvasModePen*)canvas->m_canvas->modes[(int)Canvas::kCanvasMode::Draw][0];
if (mode && canvas->m_canvas->m_current_mode == Canvas::kCanvasMode::Draw)
{
mode->m_picking = !mode->m_picking;
}
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-touchlock"))
{
button->on_click = [this](Node*) {
canvas->m_canvas->m_touch_lock = !canvas->m_canvas->m_touch_lock;
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-erase"))
{
button->on_click = [this, button](Node*) {
select_button(layout[main_id], button);
Canvas::set_mode(Canvas::kCanvasMode::Erase);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-line"))
{
button->on_click = [this, button](Node*) {
select_button(layout[main_id], button);
Canvas::set_mode(Canvas::kCanvasMode::Line);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-cam"))
{
button->on_click = [this, button](Node*) {
select_button(layout[main_id], button);
Canvas::set_mode(Canvas::kCanvasMode::Camera);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-grid"))
{
button->on_click = [this, button](Node*) {
select_button(layout[main_id], button);
Canvas::set_mode(Canvas::kCanvasMode::Grid);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-fill"))
{
button->on_click = [this, button](Node*) {
select_button(layout[main_id], button);
Canvas::set_mode(Canvas::kCanvasMode::Fill);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-mask-free"))
{
button->on_click = [this, button](Node*) {
select_button(layout[main_id], button);
Canvas::set_mode(Canvas::kCanvasMode::MaskFree);
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-mask-line"))
{
button->on_click = [this, button](Node*) {
select_button(layout[main_id], button);
Canvas::set_mode(Canvas::kCanvasMode::MaskLine);
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-bucket"))
{
button->on_click = [this](Node*) {
canvas->m_canvas->clear(ui::Canvas::I->m_current_brush.m_tip_color);
};
}
}
void App::init_menu_file()
{
if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-file"))
{
menu_file->on_click = [=](Node*) {
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
popup = (NodePopupMenu*)layout[const_hash("file-menu")]->m_children[0]->clone();
popup->update();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - popup->m_size.x + menu_file->m_size.x;
popup->SetPositioning(YGPositionTypeAbsolute);
popup->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(popup);
layout[main_id]->update();
popup->mouse_capture();
popup->m_mouse_ignore = false;
popup->m_flood_events = true;
popup->m_capture_children = false;
popup->find<NodeButtonCustom>("file-newdoc")->on_click = [this](Node*) {
dialog_newdoc();
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("file-import")->on_click = [this](Node*) {
App::I.pick_image([](std::string path){
Canvas::I->import_equirectangular(path);
});
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("file-open")->on_click = [this](Node*) {
dialog_open();
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("file-browse")->on_click = [this](Node*) {
dialog_browse();
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("file-save")->on_click = [this](Node*) {
if (ui::Canvas::I->m_newdoc)
{
dialog_save();
}
else if(ui::Canvas::I->m_unsaved)
{
canvas->m_canvas->project_save();
}
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("file-save-as")->on_click = [this](Node*) {
dialog_save();
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("file-save-ver")->on_click = [this](Node*) {
ui::Canvas::I->m_newdoc ? dialog_save() : dialog_save_ver();
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("file-export")->on_click = [this](Node*) {
dialog_export();
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("file-export-cubes")->on_click = [this](Node*) {
dialog_export_cubes();
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("file-cloud-upload")->on_click = [this](Node*) {
cloud_upload();
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("file-cloud-browse")->on_click = [this](Node*) {
cloud_browse();
popup->mouse_release();
popup->destroy();
};
};
}
}
void App::init_menu_edit()
{
if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-edit"))
{
menu_file->on_click = [=](Node*) {
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
popup = (NodePopupMenu*)layout[const_hash("edit-menu")]->m_children[0]->clone();
popup->update();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - popup->m_size.x + menu_file->m_size.x;
popup->SetPositioning(YGPositionTypeAbsolute);
popup->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(popup);
layout[main_id]->update();
popup->mouse_capture();
popup->m_mouse_ignore = false;
popup->m_flood_events = true;
popup->m_capture_children = false;
};
}
}
void App::init_menu_timelapse()
{
if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-timelapse"))
{
menu_file->on_click = [=](Node*) {
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
popup = (NodePopupMenu*)layout[const_hash("timelapse-menu")]->m_children[0]->clone();
popup->update();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - popup->m_size.x + menu_file->m_size.x;
popup->SetPositioning(YGPositionTypeAbsolute);
popup->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(popup);
layout[main_id]->update();
popup->mouse_capture();
popup->m_mouse_ignore = false;
popup->m_flood_events = true;
popup->m_capture_children = false;
if (auto item = popup->find<NodeButtonCustom>("timelapse-start"))
{
if (auto text = popup->find<NodeText>("menu-label"))
{
text->set_text(App::I.rec_running ? "Stop Recording" : "Start Recording");
}
}
popup->find<NodeButtonCustom>("timelapse-start")->on_click = [this](Node*) {
App::I.rec_running ? App::I.rec_stop() : App::I.rec_start();
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("timelapse-clear")->on_click = [this](Node*) {
App::I.rec_clear();
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("timelapse-export")->on_click = [this](Node*) {
popup->mouse_release();
popup->destroy();
App::I.rec_export("");
};
};
}
}
void App::brush_update()
{
// brushes->select_brush(canvas->m_brush.id);
// stroke->set_params(canvas->m_brush);
}
void App::init_menu_layer()
{
if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-layers"))
{
menu_file->on_click = [=](Node*) {
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
popup = (NodePopupMenu*)layout[const_hash("layers-menu")]->m_children[0]->clone();
popup->update();
if (YGNodeStyleGetDirection(layout[main_id]->y_node) == YGDirectionRTL)
pos.x = pos.x - popup->m_size.x + menu_file->m_size.x;
popup->SetPositioning(YGPositionTypeAbsolute);
popup->SetPosition(pos.x, pos.y);
layout[main_id]->add_child(popup);
layout[main_id]->update();
popup->mouse_capture();
popup->m_mouse_ignore = false;
popup->m_flood_events = true;
popup->m_capture_children = false;
popup->find<NodeButtonCustom>("clear-grids")->on_click = [this](Node*) {
CanvasModeGrid* mode = (CanvasModeGrid*)ui::Canvas::modes[(int)ui::Canvas::kCanvasMode::Grid][0];
mode->clear();
popup->mouse_release();
popup->destroy();
};
popup->find<NodeButtonCustom>("layer-clear")->on_click = [this](Node*) {
canvas->m_canvas->clear();
};
if (layers->m_current_layer)
popup->find<NodeButtonCustom>("layer-clear")->
find<NodeText>("menu-label")->
set_text(("Clear Layer " + layers->m_current_layer->m_label_text).c_str());
popup->find<NodeButtonCustom>("layer-rename")->on_click = [this](Node*) {
dialog_layer_rename();
};
if (layers->m_current_layer)
popup->find<NodeButtonCustom>("layer-rename")->
find<NodeText>("menu-label")->
set_text(("Rename Layer " + layers->m_current_layer->m_label_text).c_str());
else
popup->find<NodeButtonCustom>("layer-rename")->
find<NodeText>("menu-label")->
set_text("Rename Layer (Select a layer)");
popup->find<NodeButtonCustom>("layer-merge")->on_click = [this](Node*) {
const auto& order = canvas->m_canvas->m_order;
//layers->get_child_index(layers->)
int current_idx_order = std::distance(order.begin(), std::find(order.begin(), order.end(), canvas->m_canvas->m_current_layer_idx));
if (current_idx_order > 0)
{
int dest_layer_idx = order[current_idx_order - 1];
canvas->m_canvas->layer_merge(canvas->m_canvas->m_current_layer_idx, dest_layer_idx);
canvas->m_canvas->layer_remove(current_idx_order);
layers->clear();
for (auto& i : canvas->m_canvas->m_order)
layers->add_layer(canvas->m_canvas->m_layers[i].m_name.c_str());
layers->m_current_layer->m_selected = false;
layers->m_current_layer = layers->m_layers[current_idx_order - 1];
layers->m_current_layer->m_selected = true;
layers->m_current_layer->on_selected(layers->m_current_layer);
}
popup->mouse_release();
popup->destroy();
};
if (layers->m_current_layer)
{
const auto& order = canvas->m_canvas->m_order;
int current_idx_order = std::distance(order.begin(), std::find(order.begin(), order.end(), canvas->m_canvas->m_current_layer_idx));
if (current_idx_order > 0)
{
int down_layer_idx = order[current_idx_order - 1];
popup->find<NodeButtonCustom>("layer-merge")->
find<NodeText>("menu-label")->
set_text(("Merge with " + canvas->m_canvas->m_layers[down_layer_idx].m_name).c_str());
}
else
{
popup->find<NodeButtonCustom>("layer-merge")->
find<NodeText>("menu-label")->
set_text("Merge Layer (Select upper layers)");
}
}
else
popup->find<NodeButtonCustom>("layer-merge")->
find<NodeText>("menu-label")->
set_text("Merge Layer (Select a layer)");
};
}
}
void App::initLayout()
{
LOG("initializing layout statics");
NodeBorder::static_init();
NodeImage::static_init();
NodeIcon::static_init();
NodeStrokePreview::static_init();
layout.on_loaded = [&] {
LOG("initializing layout updating after load");
layout[main_id]->update(width, height, zoom);
LOG("initializing layout components");
init_sidebar();
canvas->m_canvas->layer_add("Default");
layers->add_layer("Default");
init_toolbar_draw();
init_toolbar_main();
init_menu_file();
init_menu_edit();
init_menu_layer();
init_menu_timelapse();
// set version string
if (auto* version_label = layout[main_id]->find<NodeText>("version"))
{
version_label->set_text(g_version);
}
if (auto* menu_entry = layout[main_id]->find<NodeButtonCustom>("menu-about"))
{
menu_entry->on_click = [=](Node*) {
// int x = 0;
// sin(time(0) / x);
};
}
Brush b;
int br_idx = brushes->find_brush("Round-Hard");
b.m_tex_id = brushes->get_texture_id(br_idx);
b.id = brushes->get_brush_id(br_idx);
b.m_tip_size = .1f;
b.m_tip_flow = .5f;
b.m_tip_spacing = .1f;
b.m_tip_opacity = 1.f;
ui::Canvas::I->m_current_brush = b;
brush_update();
TextureManager::load("data/paper.jpg");
// hacky thing to make the toolbar buttons not steal events when moving cursor fast
if (auto* toolbar = layout[main_id]->find<Node>("toolbar"))
toolbar->m_flood_events = true;
NodeImage* n = new NodeImage;
n->m_path = "data/ui/p-black.png";
n->m_tex_id = const_hash("data/ui/p-black.png");
n->SetSize(30, 45);
n->create();
NodeButtonCustom* butt = new NodeButtonCustom;
butt->create();
butt->add_child(n);
butt->SetPositioning(YGPositionTypeAbsolute);
butt->set_color({ 0, 0, 0, 0 });
//n->SetPosition(100, 100);
YGNodeStyleSetPosition(butt->y_node, YGEdgeBottom, 8);
YGNodeStyleSetPosition(butt->y_node, YGEdgeLeft, 15);
//butt->SetSize(30, 45);
layout[main_id]->add_child(butt);
butt->on_click = [this](Node*){
toggle_ui();
};
if (auto* slider = layout[main_id]->find<NodeSliderH>("frames-slider"))
{
auto frame_text = layout[main_id]->find<NodeText>("timeline-frame");
slider->on_value_changed = [this, frame_text](Node*, float value)
{
auto& c = *ui::Canvas::I;
for (int i = 0; i < c.m_layers.size(); i++)
{
auto l = layers->get_layer_at(i);
layers->handle_layer_opacity(l, .0f);
}
int current_layer = (int)glm::clamp<int>(
floor(value * c.m_layers.size()), 1, c.m_layers.size() - 1);
auto l = layers->get_layer_at(current_layer);
layers->handle_layer_selected(l);
layers->handle_layer_opacity(l, 1.f);
if (current_layer > 0)
{
auto l = layers->get_layer_at(current_layer - 1);
layers->handle_layer_opacity(l, .25f);
}
// First layer always visible
{
auto l = layers->get_layer_at(0);
layers->handle_layer_opacity(l, 1.0f);
}
if (frame_text)
{
char str[16];
snprintf(str, sizeof(str), "%02d", current_layer);
frame_text->set_text(str);
}
};
}
App::I.redraw = true;
};
LOG("initializing layout xml");
if (layout.m_loaded)
{
LOG("restore layout");
layout.restore_context();
if (panels->get_child_index(brushes.get()) == -1) brushes->restore_context();
if (panels->get_child_index(layers.get()) == -1) layers->restore_context();
if (panels->get_child_index(color.get()) == -1) color->restore_context();
if (panels->get_child_index(stroke.get()) == -1) stroke->restore_context();
}
else
layout.load("data/layout.xml");
LOG("initializing layout completed");
}

531
src/app_shaders.cpp Normal file
View File

@@ -0,0 +1,531 @@
#include "pch.h"
#include "app.h"
#include "shader.h"
using namespace ui;
void App::initShaders()
{
static const char* shader_v =
SHADER_VERSION
"uniform mat4 mvp;"
"in vec4 pos;"
"in vec2 uvs;"
"out vec3 uv;"
"void main(){"
" uv = vec3(uvs, pos.w);"
" gl_Position = mvp * vec4(pos.xyz, 1.0);"
"}";
static const char* shader_f =
SHADER_VERSION
"uniform sampler2D tex;"
"in mediump vec3 uv;"
"out mediump vec4 frag;"
"void main(){"
//" frag = texture(tex, uv.xy/uv.z);"
" frag = texture(tex, uv.xy);"
"}";
static const char* shader_uv_f =
SHADER_VERSION
"uniform sampler2D tex;"
"in mediump vec3 uv;"
"out mediump vec4 frag;"
"void main(){"
" frag = vec4(uv.xy, 0.0, 1.0);"
"}";
// TEXTURE ALPHA
static const char* shader_alpha_f =
SHADER_VERSION
"uniform sampler2D tex;\n"
"uniform mediump float alpha;\n"
"uniform bool highlight;\n"
"in mediump vec3 uv;\n"
"out mediump vec4 frag;\n"
"void main(){\n"
" mediump vec4 c = texture(tex, uv.xy);\n"
" frag = highlight ? \n"
" vec4(clamp(vec3(.3)+c.rgb, vec3(0), vec3(1)), c.a) : \n"
" texture(tex, uv.xy) * vec4(1,1,1,alpha);\n"
"}\n";
// TEXTURE ALPHA SEPARATED
static const char* shader_alpha_sep_f =
SHADER_VERSION
"uniform sampler2D tex;\n"
"uniform sampler2D tex_alpha;\n"
"uniform mediump float alpha;\n"
"uniform bool highlight;\n"
"in mediump vec3 uv;\n"
"out mediump vec4 frag;\n"
"void main(){\n"
" mediump vec3 rgb = texture(tex, uv.xy).rgb;\n"
" mediump float a = texture(tex_alpha, uv.xy).a;\n"
" mediump vec4 c = vec4(rgb, a);\n"
" frag = highlight ? \n"
" vec4(clamp(vec3(.3)+c.rgb, vec3(0), vec3(1)), c.a) : \n"
" texture(tex, uv.xy) * vec4(1,1,1,alpha);\n"
"}\n";
// STROKE PREVIEW
static const char* shader_stroke_preview_f =
SHADER_VERSION
"uniform sampler2D tex;\n"
"uniform mediump float alpha;\n"
"uniform mediump vec4 col;\n"
"in mediump vec3 uv;\n"
"out mediump vec4 frag;\n"
"void main(){\n"
" mediump float stroke = 1.0 - texture(tex, uv.xy).r;\n"
" frag = vec4(col.rgb, stroke * alpha);\n"
"}";
// TEXTURE COMP ERASE
static const char* shader_comp_erase_f =
SHADER_VERSION
"uniform sampler2D tex;\n"
"uniform sampler2D tex_stroke;\n"
"uniform sampler2D tex_mask;\n"
"uniform mediump float alpha;\n"
"uniform bool lock;\n"
"in mediump vec3 uv;\n"
"out mediump vec4 frag;\n"
"void main(){\n"
" mediump vec4 base = texture(tex, uv.xy);\n"
" mediump vec4 stroke = texture(tex_stroke, uv.xy);\n"
" mediump float a = base.a - (stroke.a * alpha);\n"
" frag = vec4(base.rgb, clamp(a, 0.0, 1.0));\n"
"}\n";
// TEXTURE COMP DRAW
static const char* shader_comp_draw_f =
SHADER_VERSION
"uniform sampler2D tex;\n"
"uniform sampler2D tex_stroke;\n"
"uniform sampler2D tex_mask;\n"
"uniform sampler2D tex_stencil;\n"
//"uniform image2D img_mixer;\n"
"uniform mediump float alpha;\n"
"uniform mediump int blend_mode;\n"
"uniform bool lock;\n"
"uniform bool mask;\n"
"in mediump vec3 uv;\n"
"out mediump vec4 frag;\n"
"mediump vec4 blur(sampler2D t, mediump vec2 uv){\n"
" mediump vec4 sum = texture(t, uv);\n"
" sum += textureOffset(t, uv, ivec2(-1, -1));\n"
" sum += textureOffset(t, uv, ivec2(-1, 0));\n"
" sum += textureOffset(t, uv, ivec2(-1, 1));\n"
" sum += textureOffset(t, uv, ivec2( 0, -1));\n"
" sum += textureOffset(t, uv, ivec2( 0, 1));\n"
" sum += textureOffset(t, uv, ivec2( 1, -1));\n"
" sum += textureOffset(t, uv, ivec2( 1, 0));\n"
" sum += textureOffset(t, uv, ivec2( 1, 1));\n"
" return sum / vec4(9.0);\n"
"}\n"
"mediump vec3 blend_normal(mediump vec4 base, mediump vec4 stroke, mediump float alpha_tot)"
"{ return mix(base.rgb, stroke.rgb, stroke.a/alpha_tot); }\n"
"mediump vec3 blend_multiply(mediump vec4 base, mediump vec4 stroke, mediump float alpha_tot)"
"{ return mix(stroke.rgb, mix(base.rgb, base.rgb*stroke.rgb, stroke.a/alpha_tot), base.a/alpha_tot); }\n"
"mediump vec3 blend_screen(mediump vec4 base, mediump vec4 stroke, mediump float alpha_tot)"
"{ return mix(stroke.rgb, mix(base.rgb, 1.0-(1.0-base.rgb)*(1.0-stroke.rgb), stroke.a/alpha_tot), base.a/alpha_tot); }\n"
"mediump vec3 blend_colorDodge(mediump vec4 base, mediump vec4 stroke, mediump float alpha_tot)"
"{ return mix(stroke.rgb, mix(base.rgb, base.rgb/(1.0-stroke.rgb), stroke.a/alpha_tot), base.a/alpha_tot); }\n"
"mediump vec3 blend_overlay(mediump vec4 base, mediump vec4 stroke, mediump float alpha_tot)"
"{ return mix(stroke.rgb, mix(base.rgb, mix(2.0*base.rgb*stroke.rgb, 1.0-2.0*(1.0-base.rgb)*(1.0-stroke.rgb), floor(base.rgb*2.0)), stroke.a/alpha_tot), base.a/alpha_tot); }\n"
"mediump vec3 blend(mediump vec4 base, mediump vec4 stroke, mediump float alpha_tot, int mode) {\n"
" if (mode == 0) return blend_normal(base, stroke, alpha_tot);\n"
" else if (mode == 1) return blend_multiply(base, stroke, alpha_tot);\n"
" else if (mode == 2) return blend_screen(base, stroke, alpha_tot);\n"
" else if (mode == 3) return blend_colorDodge(base, stroke, alpha_tot);\n"
" else if (mode == 4) return blend_overlay(base, stroke, alpha_tot);\n"
" else return blend_multiply(base, stroke, alpha_tot);\n"
"}\n"
"void main(){\n"
" mediump vec4 base = texture(tex, uv.xy);\n"
" mediump vec4 stroke = texture(tex_stroke, uv.xy);\n"
" stroke.a = mask ? stroke.a * alpha * blur(tex_mask, uv.xy).r : stroke.a * alpha;\n"
" if (!lock && base.a == 0.0) { frag = stroke; return; }\n"
" mediump float contribution = (1.0 - base.a) * stroke.a;\n"
" mediump float alpha_tot = base.a + contribution;"
" mediump vec3 rgb = blend(base, stroke, alpha_tot, blend_mode);\n"
" frag = vec4(rgb, (lock ? base.a : alpha_tot));\n"
"}\n";
// TEXTURE ATLAS
static const char* shader_atlas_v =
SHADER_VERSION
"uniform mat4 mvp;"
"uniform vec2 tof;"
"uniform vec2 tsz;"
"in vec2 pos;"
"in vec2 uvs;"
"out vec2 uv;"
"void main(){"
" uv = tof + uvs * tsz;"
" gl_Position = mvp * vec4(pos, 0.0, 1.0);"
"}";
static const char* shader_atlas_f =
SHADER_VERSION
"uniform sampler2D tex;"
"in mediump vec2 uv;"
"out mediump vec4 frag;"
"void main(){"
" frag = texture(tex, uv);"
"}";
// SOLID COLOR
static const char* shader_color_v =
SHADER_VERSION
"uniform mat4 mvp;"
"in vec4 pos;"
"void main(){"
" gl_Position = mvp * pos;"
" gl_PointSize = 5.0;"
"}";
static const char* shader_color_f =
SHADER_VERSION
"uniform mediump vec4 col;"
"out mediump vec4 frag;"
"void main(){"
" frag = col;"
"}";
// COLOR QUAD
static const char* shader_color_quad_v =
SHADER_VERSION
"uniform mat4 mvp;"
"in vec4 pos;"
"in vec2 uvs;"
"out vec3 uv;"
"void main(){"
" gl_Position = mvp * pos;"
" uv = vec3(uvs, pos.w);"
"}";
static const char* shader_color_quad_f =
SHADER_VERSION
"uniform mediump vec4 col; // HSV\n"
"in mediump vec3 uv;"
"out mediump vec4 frag;"
"mediump vec3 rgb2hsv(mediump vec3 c) {"
" mediump vec4 k = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);"
" mediump vec4 p = mix(vec4(c.bg, k.wz), vec4(c.gb, k.xy), step(c.b, c.g));"
" mediump vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));"
" mediump float d = q.x - min(q.w, q.y);"
" mediump float e = 1.0e-10;"
" return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);"
"}"
"mediump vec3 hsv2rgb(mediump vec3 c) {"
" mediump vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);"
" mediump vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);"
" return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);"
"}"
"void main() {"
" frag = vec4(hsv2rgb(vec3(col.x, uv.x, 1.0 - uv.y)), 1.0);"
"}";
// COLOR TRI
static const char* shader_color_tri_f =
SHADER_VERSION
"uniform mediump vec4 col;" // in HSV
"in mediump vec3 uv;"
"out mediump vec4 frag;"
"mediump vec3 rgb2hsv(mediump vec3 c) {"
" mediump vec4 k = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);"
" mediump vec4 p = mix(vec4(c.bg, k.wz), vec4(c.gb, k.xy), step(c.b, c.g));"
" mediump vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));"
" mediump float d = q.x - min(q.w, q.y);"
" mediump float e = 1.0e-10;"
" return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);"
"}"
"mediump vec3 hsv2rgb(mediump vec3 c) {"
" mediump vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);"
" mediump vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);"
" return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);"
"}"
"void main() {"
" mediump float sat = tan(atan(uv.y, uv.x)) *.5 + .5;"
" frag = vec4(hsv2rgb(vec3(col.r, sat, uv.x)), 1.0);"
"}";
// HUE
static const char* shader_color_hue_v =
SHADER_VERSION
"uniform mat4 mvp;"
"in vec4 pos;"
"in vec2 uvs;"
"out vec3 uv;"
"void main(){"
" gl_Position = mvp * pos;"
" uv = vec3(uvs, pos.w);"
"}";
static const char* shader_color_hue_f =
SHADER_VERSION
"uniform mediump vec4 col;"
"uniform bool dir;" // 0:horizontal, 1:vertical
"in mediump vec3 uv;"
"out mediump vec4 frag;"
"mediump vec3 rgb2hsv(mediump vec3 c) {"
" mediump vec4 k = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);"
" mediump vec4 p = mix(vec4(c.bg, k.wz), vec4(c.gb, k.xy), step(c.b, c.g));"
" mediump vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));"
" mediump float d = q.x - min(q.w, q.y);"
" mediump float e = 1.0e-10;"
" return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);"
"}"
"mediump vec3 hsv2rgb(mediump vec3 c) {"
" mediump vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);"
" mediump vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);"
" return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);"
"}"
"void main(){"
" frag = vec4(hsv2rgb(vec3(dir?uv.y:uv.x, 1.0, 1.0)), 1.0);"
"}";
// FONT
static const char* shader_font_v =
SHADER_VERSION
"uniform mat4 mvp;"
"in vec2 pos;"
"in vec2 uvs;"
"out vec2 uv;"
"void main(){"
" uv = uvs;"
" gl_Position = mvp * vec4(pos, 0.0, 1.0);"
"}";
static const char* shader_font_f =
SHADER_VERSION
"uniform mediump sampler2D tex;"
"uniform mediump vec4 col;"
"in mediump vec2 uv;"
"out mediump vec4 frag;"
"void main(){"
" mediump float a = texture(tex, uv).r;"
" frag = vec4(col.rgb, a);"
"}";
// STROKE
static const char* shader_stroke_v =
SHADER_VERSION
"uniform mat4 mvp;\n"
"in vec4 pos;\n"
"in vec2 uvs;\n"
"in vec2 uvs2;\n"
"out vec2 uv;\n"
"out vec2 uv_2;\n"
"out float q;\n"
"void main(){\n"
" uv = uvs;\n"
" uv_2 = uvs2;\n"
" q = pos.z;\n"
" gl_Position = mvp * vec4(pos.xy, 0.0, 1.0);\n"
"}\n";
static const char* shader_stroke_f =
SHADER_VERSION
#ifdef __IOS__
"#extension GL_EXT_shader_framebuffer_fetch : enable\n"
// "#extension GL_EXT_shader_image_load_store : enable\n"
#endif
"uniform mediump sampler2D tex;\n"
"uniform mediump sampler2D tex_bg;\n"
"uniform mediump sampler2D tex_stencil;\n"
"uniform mediump sampler2D tex_mix;\n"
"uniform mediump vec4 col;\n"
"uniform mediump vec2 resolution;\n"
"uniform mediump float alpha;\n"
"uniform mediump float noise;\n"
"uniform mediump vec2 stencil_offset;\n"
"uniform mediump float stencil_alpha;\n"
"uniform mediump float mix_alpha;\n"
"uniform mediump float wet;\n"
"in mediump vec2 uv;\n"
"in mediump vec2 uv_2;\n"
"in mediump float q;\n"
#ifdef __IOS__
"inout mediump vec4 frag;\n"
#else
"out mediump vec4 frag;\n"
#endif
// http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
"highp float rand(mediump vec2 co)\n"
"{\n"
" highp float a = 12.9898;\n"
" highp float b = 78.233;\n"
" highp float c = 43758.5453;\n"
" highp float dt= dot(co.xy ,vec2(a,b));\n"
" highp float sn= mod(dt,3.14);\n"
" return fract(sin(sn) * c);\n"
"}\n"
//"highp float rand(mediump vec2 co) { return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); }\n"
"void main(){\n"
" mediump vec2 uv2 = gl_FragCoord.st / resolution;\n"
" mediump float stencil = 1.0 - (texture(tex_stencil, (uv2+stencil_offset) * 5.0).r) * stencil_alpha;\n"
" mediump float brush_alpha = ( 1.0 - texture(tex, uv/q).r ) * alpha;\n"
" mediump vec4 fg = vec4(col.rgb, brush_alpha * stencil);\n"
#ifdef __IOS__
" mediump vec4 bg = frag;\n"
#else
" mediump vec4 bg = texture(tex_bg, uv2);\n"
#endif
" fg.a *= 1.0-rand(uv2+uv)*noise;\n"
" if (fg.a == 0.0) discard;\n"
" if (mix_alpha > 0.0){\n"
" mediump vec2 uv_mix = uv_2 / q;\n"
" if (uv_mix.x < 0.0 || uv_mix.x > 1.0 || uv_mix.y < 0.0 || uv_mix.y > 1.0) discard;\n"
" mediump vec4 mbg = texture(tex_mix, uv_mix);\n"
" fg.rgb = mix(fg.rgb, mbg.rgb, mix_alpha * mbg.a);\n"
" }\n"
" mediump float contribution = (1.0 - bg.a) * fg.a;\n"
" mediump float alpha_tot = bg.a + contribution;"
" mediump vec3 rgb = mix(bg.rgb, fg.rgb, fg.a / alpha_tot);\n"
" mediump vec4 frag_wet = vec4(rgb, max(bg.a, fg.a * 1.2));\n"
" mediump vec4 frag_dry = vec4(rgb, alpha_tot);\n"
" frag = mix(frag_dry, frag_wet, wet);\n"
// " mediump vec4 mbg = texture(tex_mix, uv_2 / q);\n"
// " frag.rgb = mix(frag.rgb, mbg.rgb, mix_alpha * mbg.a);\n"
"}\n";
static const char* shader_checkerboard_v =
SHADER_VERSION
"uniform mat4 mvp;\n"
"in vec4 pos;\n"
"in vec2 uvs;\n"
"out vec2 uv;\n"
"void main(){\n"
" uv = uvs;\n"
" gl_Position = mvp * vec4(pos.xyz, 1.0);\n"
"}";
static const char* shader_checkerboard_f =
SHADER_VERSION
"in mediump vec2 uv;\n"
"out mediump vec4 frag;\n"
"void main(){\n"
" const mediump vec4 c1 = vec4(1.0, 1.0, 1.0, 1.0);\n"
" const mediump vec4 c2 = vec4(0.9, 0.9, 0.9, 1.0);\n"
" mediump vec2 c = floor(fract(uv * 10.0) * 2.0);\n"
" mediump float alpha = mix(c.x, 1.0 - c.x, c.y);\n"
" frag = mix(c1, c2, alpha);\n"
"}";
static const char* shader_equirect_v =
SHADER_VERSION
"#define PI 3.1415926535897932384626433832795\n"
"#define TWO_PI 6.283185307179586476925286766559\n"
"uniform mat4 mvp;\n"
"in vec4 pos;\n"
"in vec2 uvs;\n"
"out vec2 uv;\n"
"void main(){\n"
" uv = (vec2(1.0) - uvs + vec2(0.25,0.0)) * vec2(TWO_PI, PI);\n"
" gl_Position = mvp * vec4(pos.xyz, 1.0);\n"
"}";
static const char* shader_equirect_f =
SHADER_VERSION
"uniform samplerCube tex;\n"
"in highp vec2 uv;\n"
"out mediump vec4 frag;\n"
"void main(){\n"
" highp float anglex = uv.x;\n"
" highp float angley = uv.y;\n"
" highp float sx = sin(anglex);\n"
" highp float cx = cos(anglex);\n"
" highp vec3 dir = vec3(0.0, 0.0, 0.0);\n"
" dir.x = sin(angley) * cx;\n"
" dir.y = cos(angley);\n"
" dir.z = sin(angley) * sx;\n"
" frag = texture(tex, dir);\n"
"}";
// STROKE - INSTANCED
static const char* shader_stroke_inst_v =
SHADER_VERSION
"in vec4 pos;"
"in vec2 uvs;"
"in mat4 a_mvp;"
"in float a_flow;"
"out vec3 uv;"
"out float alpha;"
"void main(){"
" uv = vec3(uvs, pos.w);"
" alpha = a_flow;"
" gl_Position = a_mvp * vec4(pos.xyz, 1.0);"
"}";
static const char* shader_stroke_inst_f =
SHADER_VERSION
"uniform mediump sampler2D tex;"
"uniform mediump sampler2D tex_stencil;\n"
"uniform mediump vec4 col;"
"uniform mediump vec2 resolution;\n"
"uniform mediump vec2 stencil_offset;\n"
"uniform mediump float stencil_alpha;\n"
"in mediump float alpha;"
"in mediump vec3 uv;"
"out mediump vec4 frag;"
"void main(){"
" mediump vec2 uv2 = gl_FragCoord.st / resolution;\n"
" mediump float stencil = 1.0 - (texture(tex_stencil, (uv2+stencil_offset)).r * 0.9) * stencil_alpha;\n"
" mediump float a = (1.0 - texture(tex, uv.xy).r) * alpha * stencil;"
" frag = vec4(col.rgb, a);"
"}";
// VERTEX COLOR
static const char* shader_vertcol_v =
SHADER_VERSION
"uniform mat4 mvp;"
"in vec4 pos;"
"in vec4 col;"
"out vec4 c;"
"void main(){"
" c = col;"
" gl_Position = mvp * pos;"
" gl_PointSize = 5.0;"
"}";
static const char* shader_vertcol_f =
SHADER_VERSION
"in mediump vec4 c;"
"out mediump vec4 frag;"
"void main(){"
" frag = c;"
"}";
LOG("initializing shaders");
if (!ShaderManager::create(kShader::Texture, shader_v, shader_f))
LOG("Failed to create shader Texture");
if (!ShaderManager::create(kShader::TextureAlpha, shader_v, shader_alpha_f))
LOG("Failed to create shader TextureAlpha");
if (!ShaderManager::create(kShader::TextureAlphaSep, shader_v, shader_alpha_sep_f))
LOG("Failed to create shader TextureAlphaSep");
if (!ShaderManager::create(kShader::StrokePreview, shader_v, shader_stroke_preview_f))
LOG("Failed to create shader StrokePreview");
if (!ShaderManager::create(kShader::CompErase, shader_v, shader_comp_erase_f))
LOG("Failed to create shader CompErase");
if (!ShaderManager::create(kShader::CompDraw, shader_v, shader_comp_draw_f))
LOG("Failed to create shader CompDraw");
if (!ShaderManager::create(kShader::Color, shader_color_v, shader_color_f))
LOG("Failed to create shader Color");
if (!ShaderManager::create(kShader::ColorQuad, shader_color_quad_v, shader_color_quad_f))
LOG("Failed to create shader ColorQuad");
if (!ShaderManager::create(kShader::ColorTri, shader_color_quad_v, shader_color_tri_f))
LOG("Failed to create shader ColorTri");
if (!ShaderManager::create(kShader::ColorHue, shader_color_hue_v, shader_color_hue_f))
LOG("Failed to create shader ColorHue");
if (!ShaderManager::create(kShader::UVs, shader_v, shader_uv_f))
LOG("Failed to create shader UVs");
if (!ShaderManager::create(kShader::Font, shader_font_v, shader_font_f))
LOG("Failed to create shader Font");
if (!ShaderManager::create(kShader::Atlas, shader_atlas_v, shader_atlas_f))
LOG("Failed to create shader Atlas");
if (!ShaderManager::create(kShader::Stroke, shader_stroke_v, shader_stroke_f))
LOG("Failed to create shader Stroke");
if (!ShaderManager::create(kShader::Checkerboard, shader_checkerboard_v, shader_checkerboard_f))
LOG("Failed to create shader Checkerboard");
if (!ShaderManager::create(kShader::Equirect, shader_equirect_v, shader_equirect_f))
LOG("Failed to create shader Equirect");
if (!ShaderManager::create(kShader::BrushStroke, shader_stroke_inst_v, shader_stroke_inst_f))
LOG("Failed to create shader BrushStroke");
if (!ShaderManager::create(kShader::VertexColor, shader_vertcol_v, shader_vertcol_f))
LOG("Failed to create shader VertexColor");
LOG("shaders initialized");
}

193
src/asset.cpp Normal file
View File

@@ -0,0 +1,193 @@
#include "pch.h"
#include "log.h"
#include "asset.h"
#ifdef __APPLE__
#include <Foundation/Foundation.h>
#endif
#ifdef __ANDROID__
#include <dirent.h>
AAssetManager* Asset::m_am;
#endif
bool Asset::delete_file(const std::string& path)
{
LOG("delete project: %s", path.c_str());
std::remove(path.c_str());
return true;
}
bool Asset::exist(std::string path, bool is_asset)
{
if (is_asset)
{
Asset asset;
if (asset.open(path.c_str()))
{
asset.close();
return true;
}
}
else
{
std::ifstream f(path);
return f.is_open();
}
return false; // useless return for the stupid xcode
}
std::vector<std::string> Asset::list_files(std::string folder, bool is_asset, const std::string& filter_regex)
{
std::vector<std::string> names;
#ifdef _WIN32
WIN32_FIND_DATAA fd;
HANDLE hFind = ::FindFirstFileA((folder + "\\*").c_str(), &fd);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
// read all (real) files in current folder
// , delete '!' read other 2 default folder . and ..
if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
names.push_back(fd.cFileName);
}
} while (::FindNextFileA(hFind, &fd));
::FindClose(hFind);
}
#elif __ANDROID__
if (is_asset)
{
AAssetDir* dir = AAssetManager_openDir(Asset::m_am, folder.c_str());
while (const char* name = AAssetDir_getNextFileName(dir))
{
//LOG("asset: %s", name);
names.push_back(name);
}
AAssetDir_close(dir);
}
else
{
DIR *dp;
struct dirent *ep;
dp = opendir(folder.c_str());
if (dp != NULL)
{
while ((ep = readdir(dp)))
if (ep->d_type != DT_DIR)
names.push_back(ep->d_name);
closedir(dp);
}
else
LOG("Couldn't open the directory: %s", folder.c_str());
}
#else
std::string abs_path = folder;
if (is_asset)
{
NSString* bundle_path = [[NSBundle mainBundle] resourcePath];
std::string base = [bundle_path cStringUsingEncoding : 1];
abs_path = base + "/" + folder;
}
DIR *dp;
struct dirent *ep;
dp = opendir(abs_path.c_str());
if (dp != NULL)
{
while ((ep = readdir(dp)))
if (ep->d_type != DT_DIR)
names.push_back(ep->d_name);
closedir(dp);
}
else
LOG("Couldn't open the directory: %s", folder.c_str());
#endif
if (!filter_regex.empty())
{
std::regex r(filter_regex);
names.erase(std::remove_if(names.begin(), names.end(),
[&r](const std::string& s) { return !std::regex_match(s, r); }), names.end());
}
return names;
}
std::string Asset::absolute(const std::string& path)
{
#ifdef __APPLE__
NSString* bundle_path = [[NSBundle mainBundle] resourcePath];
std::string base = [bundle_path cStringUsingEncoding:1];
return base + "/" + path;
#else
return path;
#endif
}
bool Asset::open(const char* path)
{
//LOG("Asset::open %s", path);
m_current_path = path;
std::string file_path = path;
#ifdef __ANDROID__
if (!(m_asset = AAssetManager_open(m_am, path, AASSET_MODE_RANDOM)))
{
LOG("AAssetManager_open failed");
return false;
}
m_len = (int)AAsset_getLength(m_asset);
m_data = (uint8_t*)AAsset_getBuffer(m_asset);
#else
#ifdef __APPLE__
NSString* bundle_path = [[NSBundle mainBundle] resourcePath];
std::string base = [bundle_path cStringUsingEncoding:1];
file_path = base + "/" + path;
#endif
//LOG("asset file: %s", file_path.c_str());
if (!(m_fp = fopen(file_path.c_str(), "rb")))
{
LOG("errno = %d", errno);
return false;
}
fseek(m_fp, 0, SEEK_END);
m_len = (int)ftell(m_fp);
fseek(m_fp, 0, SEEK_SET);
#endif
return true;
}
glm::uint8_t* Asset::read_all()
{
#ifdef __ANDROID__
return m_data;
#else
if (!m_data)
{
m_data = new uint8_t[m_len];
if (m_len != fread(m_data, 1, m_len, m_fp))
{
LOG("ASSET READ FAILED for %s", m_current_path.c_str());
delete m_data;
m_data = nullptr;
}
}
return m_data;
#endif
}
void Asset::close()
{
#ifdef __ANDROID__
AAsset_close(m_asset);
#else
if (m_fp)
fclose(m_fp);
if (m_data)
delete m_data;
m_data = nullptr;
#endif
}

23
src/asset.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
class Asset
{
public:
#ifdef __ANDROID__
static AAssetManager* m_am;
AAsset* m_asset = nullptr;
#endif
static std::vector<std::string> list_files(std::string folder, bool is_asset, const std::string& filter_regex);
static bool exist(std::string path, bool is_asset);
static bool delete_file(const std::string& path);
static std::string absolute(const std::string& path);
std::string m_current_path;
FILE* m_fp = nullptr;
int m_len = 0;
uint8_t* m_data = nullptr;
bool open(const char* path);
uint8_t* read_all();
void close();
};

40
src/bezier.cpp Normal file
View File

@@ -0,0 +1,40 @@
#include "pch.h"
#include "log.h"
#include "bezier.h"
double BezierCurve::FactorialLookup[] = {
1.0,
1.0,
2.0,
6.0,
24.0,
120.0,
720.0,
5040.0,
40320.0,
362880.0,
3628800.0,
39916800.0,
479001600.0,
6227020800.0,
87178291200.0,
1307674368000.0,
20922789888000.0,
355687428096000.0,
6402373705728000.0,
121645100408832000.0,
2432902008176640000.0,
51090942171709440000.0,
1124000727777607680000.0,
25852016738884976640000.0,
620448401733239439360000.0,
15511210043330985984000000.0,
403291461126605635584000000.0,
10888869450418352160768000000.0,
304888344611713860501504000000.0,
8841761993739701954543616000000.0,
265252859812191058636308480000000.0,
8222838654177922817725562880000000.0,
263130836933693530167218012160000000.0,
};

66
src/bezier.h Normal file
View File

@@ -0,0 +1,66 @@
#pragma once
class BezierCurve
{
static double FactorialLookup[33];
public:
// just check if n is appropriate, then return the result
static double factorial(int n)
{
// if (n < 0) { throw new Exception("n is less than 0"); }
// if (n > 32) { throw new Exception("n is greater than 32"); }
return FactorialLookup[n]; /* returns the value n! as a SUMORealing point number */
}
static double Ni(int n, int i)
{
double ni;
double a1 = factorial(n);
double a2 = factorial(i);
double a3 = factorial(n - i);
ni = a1 / (a2 * a3);
return ni;
}
// Calculate Bernstein basis
static double Bernstein(int n, int i, double t)
{
double basis;
double ti; /* t^i */
double tni; /* (1 - t)^i */
/* Prevent problems with pow */
if (t == 0.0 && i == 0)
ti = 1.0;
else
ti = pow(t, i);
if (n == i && t == 1.0)
tni = 1.0;
else
tni = pow((1 - t), (n - i));
//Bernstein basis
basis = Ni(n, i) * ti * tni;
return basis;
}
static glm::vec2 Bezier2D(const std::vector<glm::vec2>& b, double t)
{
// if ((1.0 - t) < 5e-6)
// t = 1.0;
double px = 0.0;
double py = 0.0;
const int npts = (int)b.size();
for (int i = 0; i < npts; i++)
{
double basis = Bernstein(npts - 1, i, t);
px += basis * b[i].x;
py += basis * b[i].y;
}
return { px, py };
}
};

240
src/brush.cpp Normal file
View File

@@ -0,0 +1,240 @@
#include "pch.h"
#include "log.h"
#include "brush.h"
#include "canvas.h"
void ui::BrushMesh::draw(const std::vector<StrokeSample>& samples, const glm::mat4& proj)
{
std::vector<instance_t> attributes;
attributes.reserve(samples.size());
for (const auto& s : samples)
{
auto mvp = proj *
glm::translate(glm::vec3(s.pos, 0)) *
glm::scale(glm::vec3(s.size, s.size, 1)) *
glm::eulerAngleZ(s.angle);
attributes.emplace_back(instance_t{ mvp, s.flow });
}
#ifdef USE_VBO
glBindBuffer(GL_ARRAY_BUFFER, buffers[2]);
glBufferData(GL_ARRAY_BUFFER, (int)(sizeof(instance_t) * attributes.size()), attributes.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(vao);
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0, (int)samples.size());
glBindVertexArray(0);
#else
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, pos));
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, uvs));
// Likewise, we can do the same with the model matrix. Note that a
// matrix input to the vertex shader consumes N consecutive input
// locations, where N is the number of columns in the matrix. So...
// we have four vertex attributes to set up.
glBindBuffer(GL_ARRAY_BUFFER, buffers[2]);
glBufferData(GL_ARRAY_BUFFER, (int)(sizeof(instance_t) * attributes.size()), attributes.data(), GL_STATIC_DRAW);
// Loop over each column of the matrix...
for (int i = 0; i < 4; i++)
{
// Set up the vertex attribute
glVertexAttribPointer(loc_mvp + i, 4, GL_FLOAT, GL_FALSE, sizeof(instance_t),
(GLvoid*)(offsetof(instance_t, mvp) + sizeof(glm::vec4) * i));
// Enable it
glEnableVertexAttribArray(loc_mvp + i);
// Make it instanced
glVertexAttribDivisor(loc_mvp + i, 1);
}
glEnableVertexAttribArray(loc_flow);
glVertexAttribPointer(loc_flow, 1, GL_FLOAT, GL_FALSE, sizeof(instance_t),
(GLvoid*)offsetof(instance_t, flow));
glVertexAttribDivisor(loc_flow, 1);
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0, (int)samples.size());
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
for (int i = 0; i < 4; i++)
glDisableVertexAttribArray(loc_mvp + i);
glDisableVertexAttribArray(loc_flow);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
#endif // USE_VBO
}
bool ui::BrushMesh::create()
{
static GLushort idx[6]{ 0, 1, 2, 0, 2, 3 };
static vertex_t vertices[4]{
{ { -.5f, -.5f, 0, 1 }, { 0, 0 } }, // A B----C
{ { -.5f, .5f, 0, 1 }, { 0, 1 } }, // B --\ | |
{ { .5f, .5f, 0, 1 }, { 1, 1 } }, // C --/ | |
{ { .5f, -.5f, 0, 1 }, { 1, 0 } }, // D A----D
};
glGenBuffers(3, buffers);
if (!(buffers[0] && buffers[1] && buffers[2]))
return false;
static instance_t inst{ glm::mat4(), .1f };
glBindBuffer(GL_ARRAY_BUFFER, buffers[2]);
glBufferData(GL_ARRAY_BUFFER, sizeof(instance_t), &inst, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idx), idx, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
auto shader = ShaderManager::get(kShader::BrushStroke);
loc_flow = shader->GetAttribLocation("a_flow");
loc_mvp = shader->GetAttribLocation("a_mvp");
#if USE_VBO
glGenVertexArrays(1, &vao);
if (!vao)
return false;
glBindVertexArray(vao);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, pos));
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, uvs));
glBindBuffer(GL_ARRAY_BUFFER, buffers[2]);
// Loop over each column of the matrix...
for (int i = 0; i < 4; i++)
{
// Set up the vertex attribute
glVertexAttribPointer(loc_mvp + i, 4, GL_FLOAT, GL_FALSE, sizeof(instance_t),
(GLvoid*)(offsetof(instance_t, mvp) + sizeof(glm::vec4) * i));
// Enable it
glEnableVertexAttribArray(loc_mvp + i);
// Make it instanced
glVertexAttribDivisor(loc_mvp + i, 1);
}
glEnableVertexAttribArray(loc_flow);
glVertexAttribPointer(loc_flow, 1, GL_FLOAT, GL_FALSE, sizeof(instance_t),
(GLvoid*)offsetof(instance_t, flow));
glVertexAttribDivisor(loc_flow, 1);
glBindVertexArray(0);
#endif
return true;
}
ui::StrokeSample ui::Stroke::randomize_sample(const glm::vec2& pos, float pressure, float curve_angle)
{
auto rnd_nor = [&] { return float((double)prng() / (double)prng.max()); }; // normalized [0, +1]
//auto rnd_neg = [&] { return float((double)prng() / (double)prng.max() * 2.0 - 1.0); }; // normalized [-1, +1]
auto rnd_rad = [&] { return float((double)prng() / (double)prng.max() * M_PI * 2.0); }; // normalized [0, 2pi]
auto rnd_vec = [&] { float rad = rnd_rad(); return glm::vec2(cosf(rad), sinf(rad)); }; // normalized direction vector
float size_dyn = m_brush.m_tip_size_pressure ? pressure : 1.f;
float flow_dyn = m_brush.m_tip_flow_pressure ? pressure : 1.f;
StrokeSample s;
s.origin = pos;
s.angle = -curve_angle + (m_brush.m_tip_angle + rnd_nor() * m_brush.m_jitter_angle) * (float)(M_PI * 2.0);
s.pos = pos + (rnd_vec() * m_brush.m_jitter_spread * 100.f);
s.size = 800.f * m_brush.m_tip_size * (1.f - rnd_nor() * m_brush.m_jitter_scale) * size_dyn;
s.flow = m_brush.m_tip_flow * (1.f - rnd_nor() * m_brush.m_jitter_flow) * flow_dyn;
auto hsv = convert_rgb2hsv(m_brush.m_tip_color);
hsv.x = glm::clamp(glm::mix(hsv.x, (pressure - 0.5f) * 2.0f, m_brush.m_tip_hue * (float)m_brush.m_tip_hue_pressure) + (rnd_nor() - 0.5f) * m_brush.m_jitter_hue, 0.f, 1.f);
hsv.y = glm::clamp(glm::mix(hsv.y, (1.f - pressure - 0.5f) * 2.0f, m_brush.m_tip_sat * (float)m_brush.m_tip_sat_pressure) + (rnd_nor() - 0.5f) * m_brush.m_jitter_sat, 0.f, 1.f);
hsv.z = glm::clamp(glm::mix(hsv.z, (pressure - 0.5f) * 2.0f, m_brush.m_tip_val * (float)m_brush.m_tip_val_pressure) + (rnd_nor() - 0.5f) * m_brush.m_jitter_val, 0.f, 1.f);
m_hsv_jitter.add(hsv);
s.col = convert_hsv2rgb(m_hsv_jitter.average());
return s;
}
std::vector<ui::StrokeSample> ui::Stroke::compute_samples()
{
if (m_keypoints.empty()) return {};
int nsamples = (int)glm::floor((m_keypoints.back().dist - m_dist) / m_step);
std::vector<StrokeSample> samples;
samples.reserve(nsamples); // preallocate the estimate number of samples
while (m_keypoints.back().dist > (m_dist + m_step))
{
bool is_first = m_last_kp == 0;
m_dist += m_step;
while (m_dist > m_keypoints[m_last_kp + 1].dist)
m_last_kp++;
const auto& A = m_keypoints[m_last_kp];
const auto& B = m_keypoints[m_last_kp + 1]; // NOTE: this should be true when while is true
float t = (m_dist - A.dist) / (B.dist - A.dist); // NOTE: must be A != B
auto pos = glm::lerp(A.pos, B.pos, t);
float pressure = glm::lerp(A.pressure, B.pressure, t);
auto s = randomize_sample(pos, pressure, 0);
if (m_brush.m_tip_angle_follow)
{
auto& pre = m_prev_sample;
glm::vec2 v = glm::normalize(s.origin - pre.origin);
float curve_angle = -glm::orientedAngle(v, glm::vec2(1, 0));
// NOTE: average angles need correction for 0-360 discontinuity
//m_curve_angles.add(curve_angle);
//float avg = m_curve_angles.average();
s.angle += curve_angle;
}
m_prev_sample = s;
if (!s.valid())
LOG("Invalid sample");
samples.push_back(s);
}
return std::move(samples);
}
bool ui::Stroke::has_sample()
{
return m_keypoints.empty() ? false : // no keypoints
(m_keypoints.back().dist > (m_dist + m_step)); // check if next kp is closer than spacing
}
void ui::Stroke::reset(bool clear_keypoints /*= false*/)
{
m_last_kp = 0;
m_dist = 0.f;
if (clear_keypoints)
m_keypoints.clear();
}
void ui::Stroke::add_point(glm::vec2 pos, float pressure)
{
#ifdef __IOS__
m_curve = glm::min(m_curve + 0.1f, 1.f);
pressure = pressure * glm::pow(m_curve, 2.f);
#endif // __IOS__
m_pressure_buff.add(pressure);
pressure = m_pressure_buff.average();
if (m_brush.m_tip_size_pressure)
m_step = glm::max(m_brush.m_tip_spacing * m_brush.m_tip_size * pressure * 800.f, 1.f);
float dist = m_keypoints.empty() ? m_step :
m_keypoints.back().dist + glm::distance(m_keypoints.back().pos, pos);
if (m_keypoints.empty())
m_prev_sample.origin = pos;
else if (m_keypoints.back().pos == pos)
return; // skip same point, leading to black samples (NaN values)
Keypoint kp;
kp.pos = pos;
kp.pressure = pressure;
kp.dist = dist;
m_keypoints.push_back(kp);
}
void ui::Stroke::start(const ui::Brush& brush)
{
m_curve = 0.f;
m_curve_angles.clear();
m_pressure_buff.clear();
m_hsv_jitter.clear();
m_last_kp = 0;
m_dist = 0.f;
m_brush = brush;
m_brush.m_tip_size *= 1.f / glm::tan(glm::radians(Canvas::I->m_cam_fov * 0.5f));
m_step = glm::max(m_brush.m_tip_spacing * m_brush.m_tip_size * 800.f, 1.f);
prng.seed(0);
}

110
src/brush.h Normal file
View File

@@ -0,0 +1,110 @@
#pragma once
#include "rtt.h"
#include "shader.h"
NS_START
class Brush
{
public:
int id = 0;
std::string m_name;
uint16_t m_tex_id = 0;
glm::vec4 m_tip_color{1, 0, 0, 1};
float m_tip_size = 0;
float m_tip_spacing = 0;
float m_tip_flow = 0;
float m_tip_opacity = 0;
float m_tip_angle = 0;
float m_tip_mix = 0;
float m_tip_stencil = 0;
float m_tip_wet = 0;
float m_tip_noise = 0;
float m_tip_hue = 0;
float m_tip_sat = 0;
float m_tip_val = 0;
bool m_tip_angle_follow = false;
bool m_tip_flow_pressure = false;
bool m_tip_size_pressure = false;
bool m_tip_hue_pressure = false;
bool m_tip_sat_pressure = false;
bool m_tip_val_pressure = false;
float m_jitter_scale = 0;
float m_jitter_angle = 0;
float m_jitter_spread = 0;
float m_jitter_flow = 0;
float m_jitter_hue = 0;
float m_jitter_sat = 0;
float m_jitter_val = 0;
int m_blend_mode = 0;
};
struct StrokeSample
{
glm::vec3 col = { 0, 0, 0 };
glm::vec2 pos = { 0, 0 };
glm::vec2 origin = { 0,0 };
float size = 0;
float flow = 0;
float angle = 0;
bool valid() const
{
return !(
glm::any(glm::isnan(col)) ||
glm::any(glm::isnan(pos)) ||
glm::any(glm::isnan(origin))
);
}
};
class BrushMesh
{
public:
GLuint buffers[3]{ 0 };
GLuint vao{ 0 };
struct vertex_t { glm::vec4 pos; glm::vec2 uvs; };
struct instance_t { glm::mat4 mvp; float flow; };
int loc_flow = 0;
int loc_mvp = 0;
bool create();
void draw(const std::vector<StrokeSample>& samples, const glm::mat4& proj);
};
class Stroke
{
public:
struct Keypoint
{
glm::vec2 pos = { 0, 0 };
float pressure = 0;
float dist = 0;
};
struct Camera
{
glm::vec2 rot = { 0, 0 };
float fov = 0;
};
int m_layer = 0;
float m_curve = 0;
float m_dist = 0;
float m_step = 0;
Camera m_camera;
ui::Brush m_brush;
cbuffer<float, 3> m_curve_angles;
cbuffer<float, 10> m_pressure_buff;
cbuffer<glm::vec3, 3> m_hsv_jitter;
StrokeSample m_prev_sample;
std::vector<Keypoint> m_keypoints;
std::vector<StrokeSample> m_samples;
int m_last_kp;
std::minstd_rand prng;
void start(const ui::Brush& brush);
void add_point(glm::vec2 pos, float pressure);
void reset(bool clear_keypoints = false);
bool has_sample();
std::vector<StrokeSample> compute_samples();
StrokeSample randomize_sample(const glm::vec2& pos, float pressure, float curve_angle);
};
NS_END

2134
src/canvas.cpp Normal file

File diff suppressed because it is too large Load Diff

234
src/canvas.h Normal file
View File

@@ -0,0 +1,234 @@
#pragma once
#include "rtt.h"
#include "texture.h"
#include "shader.h"
#include "shape.h"
#include "brush.h"
#include "action.h"
#include "canvas_modes.h"
NS_START
#define CANVAS_RES 512
class Layer
{
public:
RTT m_rtt[6];
glm::vec4 m_dirty_box[6] = SIXPLETTE(glm::vec4(0));
bool m_dirty_face[6] = SIXPLETTE(false);
bool m_visible = true;
bool m_alpha_locked = false;
float m_opacity = 1.f;
bool m_hightlight = false;
std::string m_name;
int w = 0;
int h = 0;
struct Snapshot
{
std::unique_ptr<uint8_t[]> image[6] = SIXPLETTE(0);
glm::vec4 m_dirty_box[6] = SIXPLETTE(glm::vec4(0));
bool m_dirty_face[6] = SIXPLETTE(false);
void create(int w, int h)
{
for (int i = 0; i < 6; i++)
image[i] = std::make_unique<uint8_t[]>(w*h*4);
}
};
bool create(int width, int height, std::string name);
void clear(const glm::vec4& c);
Snapshot snapshot(std::string data_path);
void restore(const Snapshot& snap);
void destroy();
};
class Canvas
{
public:
Plane m_plane;
Plane m_plane_brush;
BrushMesh m_mesh;
bool m_unsaved = false;
bool m_newdoc = true;
bool m_dirty = false;
bool m_commit_delayed = false;
bool m_dirty_stroke = false;
static Canvas* I;
NodeCanvas* m_node = nullptr;
bool m_alpha_lock = false;
bool m_touch_lock = true;
glm::mat4 m_mv{ 1 };
glm::mat4 m_proj{ 1 };
glm::vec4 m_box{ 0 };
glm::vec4 m_vp{ 0 };
glm::vec2 m_pan{ 0 };
int m_width = 0;
int m_height = 0;
bool m_use_instanced = false;
int m_current_layer_idx = 0;
std::unique_ptr<Stroke> m_current_stroke;
bool m_show_tmp = false;
std::vector<Layer> m_layers;
std::vector<int> m_order;
glm::vec4 m_dirty_box[6] = SIXPLETTE(glm::vec4(0));
bool m_dirty_face[6] = SIXPLETTE(false);
Layer m_smask; // selection mask
bool m_smask_active = false;
RTT m_tmp[6];
RTT m_mixer;
float m_mixer_scale = 1;
ui::StrokeSample m_mixer_sample;
bool m_mixer_idle = true;
Texture2D m_brush_mix;
Texture2D m_tex[6];
Texture2D m_tex2[6];
bool m_pick_ready[6];
std::unique_ptr<glm::u8vec4[]> m_pick_data[6] = SIXPLETTE(nullptr);
static glm::vec3 m_plane_origin[6];
static glm::vec3 m_plane_normal[6];
static glm::vec3 m_plane_tangent[6];
static glm::mat4 m_plane_transform[6];
glm::vec2 stencil_offset;
Sampler m_sampler;
Sampler m_sampler_linear;
Sampler m_sampler_brush;
Sampler m_sampler_bg;
Sampler m_sampler_mask;
Sampler m_sampler_stencil;
Sampler m_sampler_mix;
glm::vec2 m_cam_rot{ 0 };
glm::vec3 m_cam_pos{ 0 };
float m_cam_fov = 85;
glm::vec2 m_cur_pos;
Brush m_current_brush;
enum class kCanvasMode { Draw, Erase, Line, Camera, Grid, Fill, MaskFree, MaskLine, COUNT };
kCanvasMode m_state{ kCanvasMode::Draw };
static std::vector<CanvasMode*> modes[];
std::vector<CanvasMode*>* m_mode = nullptr;
kCanvasMode m_current_mode = kCanvasMode::Draw;
static void set_mode(kCanvasMode mode)
{
if (I->m_mode)
for (auto& m : *I->m_mode)
m->leave();
I->m_mode = &modes[(int)mode];
I->m_state = mode;
I->m_current_mode = mode;
if (I->m_mode)
for (auto& m : *I->m_mode)
m->enter();
}
std::vector<Layer::Snapshot> m_layers_snapshot;
Canvas() { I = this; }
bool create(int width, int height);
void resize(int width, int height);
void layer_remove(int idx);
void layer_add(std::string name);
void layer_order(int idx, int pos);
void layer_merge(int source_idx, int dest_idx);
void stroke_start(glm::vec2 point, float pressure, const ui::Brush& brush);
void stroke_update(glm::vec2 point, float pressure);
void stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz);
void stroke_draw();
void stroke_end();
void stroke_cancel();
void stroke_commit();
void clear(const glm::vec4& color = { 1, 1, 1, 0 });
void pick_start();
void pick_update(int plane);
glm::vec4 pick_get(glm::vec2 canvas_loc);
void pick_end();
void snapshot_save(std::string data_path);
void snapshot_restore();
void snap_history(const std::vector<int>& planes);
class ActionStroke* create_action(int layer);
void clear_context();
void import_equirectangular(std::string file_path);
void import_equirectangular_thread(std::string file_path);
void export_equirectangular(std::string file_path);
void export_equirectangular_thread(std::string file_path);
void export_anim(std::string data_path);
void export_cubes(std::string data_path);
void project_save(std::function<void()> on_complete = nullptr);
void project_save(std::string file_path, std::function<void()> on_complete = nullptr);
void project_save_thread(std::string file_path);
void project_open(std::string file_path, std::function<void()> on_complete = nullptr);
void project_open_thread(std::string file_path);
void inject_xmp(std::string jpg_path);
ui::Image thumbnail_generate(int w, int h);
ui::Image thumbnail_read(std::string data_path);
void draw_objects(std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)>);
void draw_objects(std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)>, Layer& layer);
bool ray_intersect(glm::vec3 ray_origin, glm::vec3 ray_dir, glm::vec3 plane_origin,
glm::vec3 plane_normal, glm::vec3 plane_tangent, glm::vec3 &out_hit);
void point_unproject(glm::vec2 loc, glm::vec4 vp, glm::mat4 camera, glm::mat4 proj,
glm::vec3 &out_origin, glm::vec3 &out_dir);
bool point_trace(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
glm::vec3& hit_pos, glm::vec2& fb_pos, glm::vec3& hit_normal, int& out_plane_id);
bool point_trace_plane(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
glm::vec3& hit_pos, glm::vec3& hit_normal, glm::vec2& hit_fb_pos, int plane_id);
std::vector<ui::Shape::vertex_t> triangulate(const std::vector<std::shared_ptr<p2t::Point>>& points);
void project2Dpoints(std::vector<ui::Shape::vertex_t>& vertices);
};
class ActionStroke : public Action
{
public:
std::unique_ptr<Stroke> m_stroke;
std::unique_ptr<uint8_t[]> m_image[6] = SIXPLETTE(nullptr);
glm::ivec4 m_old_box[6] = SIXPLETTE(glm::ivec4(0));
bool m_old_dirty[6] = SIXPLETTE(false);
glm::ivec4 m_box[6] = SIXPLETTE(glm::ivec4(0));
bool m_dirty[6] = SIXPLETTE(false);
bool clear_layer = false;
int m_layer_idx;
Canvas* m_canvas;
virtual void run() override
{
}
virtual Action* get_redo()
{
auto redo = m_canvas->create_action(m_layer_idx);
return redo;
}
virtual void undo() override
{
if (clear_layer)
m_canvas->m_layers[m_layer_idx].clear({ 0, 0, 0, 0 });
for (int i = 0; i < 6; i++)
{
// empty data
if (!m_image[i])
continue;
m_canvas->m_layers[m_layer_idx].m_dirty_box[i] = m_old_box[i];
m_canvas->m_layers[m_layer_idx].m_dirty_face[i] = m_old_dirty[i];
m_canvas->m_layers[m_layer_idx].m_rtt[i].bindTexture();
glm::vec2 box_sz = zw(m_box[i]) - xy(m_box[i]);
glTexSubImage2D(GL_TEXTURE_2D, 0, (int)m_box[i].x, (int)m_box[i].y, (int)box_sz.x, (int)box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, m_image[i].get());
m_canvas->m_layers[m_layer_idx].m_rtt[i].unbindTexture();
}
}
virtual size_t memory() override
{
size_t mem = 0;
for (int i = 0; i < 6; i++)
{
glm::ivec2 sz = zw(m_box[i]) - xy(m_box[i]);
mem += sz.x * sz.y * 4 + sizeof(*this);
}
return mem;
}
virtual ~ActionStroke()
{
}
};
NS_END

788
src/canvas_modes.cpp Normal file
View File

@@ -0,0 +1,788 @@
#include "pch.h"
#include "log.h"
#include "canvas_modes.h"
#include "layout.h"
#include "canvas.h"
#include "shader.h"
#include "node_canvas.h"
#include "app.h"
#include "util.h"
NodeCanvas* CanvasMode::node;
ui::Canvas* CanvasMode::canvas;
void CanvasModeBasicCamera::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
switch (me->m_type)
{
case kEventType::MouseDownL:
break;
case kEventType::MouseUpL:
break;
case kEventType::MouseDownR:
if (App::I.keys[(int)kKey::KeyAlt])
break;
m_draggingR = true;
m_dragR_start = me->m_pos;
m_pan_start = canvas->m_pan;
node->mouse_capture();
break;
case kEventType::MouseUpR:
m_draggingR = false;
node->mouse_release();
break;
case kEventType::MouseMove:
if (m_draggingR)
canvas->m_pan = m_pan_start + (me->m_pos - m_dragR_start) * glm::vec2(-1, -1) * (canvas->m_cam_fov / 85.f);
canvas->m_cam_rot = canvas->m_pan * 0.003f;
break;
case kEventType::MouseScroll:
m_zoom_canvas += me->m_scroll_delta * 0.1f;
canvas->m_cam_fov -= me->m_scroll_delta * 2.0f;
App::I.brush_update();
break;
case kEventType::MouseCancel:
m_draggingR = false;
node->mouse_release();
break;
default:
break;
}
}
void CanvasModeBasicCamera::on_GestureEvent(GestureEvent* ge)
{
switch (ge->m_type)
{
case kEventType::GestureStart:
m_pan_start = canvas->m_pan;
m_zoom_start = m_zoom_canvas;
m_camera_fov = canvas->m_cam_fov;
break;
case kEventType::GestureMove:
canvas->m_pan = m_pan_start + ge->m_pos_delta * glm::vec2(-1, -1) * 0.3f * (canvas->m_cam_fov / 85.f);
canvas->m_cam_fov = m_camera_fov - ge->m_distance_delta * .05f;
canvas->m_cam_rot = canvas->m_pan * 0.003f;
App::I.brush_update();
break;
default:
break;
}
}
////////////////////////////////////////////////////////////////////
void CanvasModePen::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
switch (me->m_type)
{
case kEventType::MouseDownL:
if (App::I.keys[(int)kKey::KeyAlt] || m_picking)
{
m_picking = true;
canvas->pick_start();
glm::vec4 pix = canvas->pick_get(loc);
canvas->m_current_brush.m_tip_color = pix;
App::I.color->set_color(pix);
}
else
{
canvas->stroke_start(loc, me->m_pressure, canvas->m_current_brush);
}
m_dragging = true;
node->mouse_capture();
break;
case kEventType::MouseUpL:
if (m_dragging && !m_picking)
{
node->mouse_release();
canvas->stroke_end();
}
if (m_dragging && m_picking)
{
node->mouse_release();
glm::vec4 pix = canvas->pick_get(loc);
canvas->m_current_brush.m_tip_color = pix;
App::I.color->set_color(pix);
canvas->pick_end();
}
m_dragging = false;
m_picking = false;
break;
case kEventType::MouseDownR:
if (App::I.keys[(int)kKey::KeyAlt])
{
m_resizing = true;
m_dragging = true;
m_size_pos_start = m_cur_pos;
m_size_value_start = canvas->m_current_brush.m_tip_size;
node->mouse_capture();
}
break;
case kEventType::MouseUpR:
if (m_dragging && m_resizing)
{
node->mouse_release();
m_dragging = false;
m_resizing = false;
}
break;
case kEventType::MouseMove:
if (m_dragging && !m_picking && !m_resizing)
canvas->stroke_update(loc, me->m_pressure);
if (m_dragging && m_picking)
{
glm::vec4 pix = canvas->pick_get(loc);
canvas->m_current_brush.m_tip_color = pix;
App::I.color->set_color(pix);
}
if (m_dragging && m_resizing)
{
auto diff = m_cur_pos - m_size_pos_start;
canvas->m_current_brush.m_tip_size = m_size_value_start + diff.x * 0.001f;
}
m_cur_pos = loc;
break;
case kEventType::MouseCancel:
if (m_dragging)
{
canvas->stroke_cancel();
m_dragging = false;
node->mouse_release();
}
if (m_picking)
m_picking = false;
if (m_resizing)
m_resizing = false;
break;
default:
break;
}
}
void CanvasModePen::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
#ifndef __IOS__
//if (!m_dragging)
{
auto pos = m_resizing ? m_size_pos_start : m_cur_pos;
if (App::I.keys[(int)kKey::KeyAlt] && !m_resizing)
pos.x = pos.x - canvas->m_current_brush.m_tip_size * 500;
ui::ShaderManager::use(ui::kShader::StrokePreview);
ui::ShaderManager::u_int(ui::kShaderUniform::Tex, 0);
ui::ShaderManager::u_float(ui::kShaderUniform::Alpha, canvas->m_current_brush.m_tip_flow);
auto tip_color = glm::vec4(glm::vec3(canvas->m_current_brush.m_tip_color), 1);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, tip_color);
//ui::ShaderManager::u_int(ui::kShaderUniform::Highlight, 0);
float tip_scale = 1.f / glm::tan(glm::radians(ui::Canvas::I->m_cam_fov * 0.5f));
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP,
glm::scale(glm::vec3(1, -1, 1)) *
ortho *
glm::translate(glm::vec3(pos, 0)) *
glm::scale(glm::vec3(canvas->m_current_brush.m_tip_size * 800.f * tip_scale))
);
glEnable(GL_BLEND);
glActiveTexture(GL_TEXTURE0);
auto& tex = TextureManager::get(canvas->m_current_brush.m_tex_id);
tex.bind();
canvas->m_sampler_brush.bind(0);
canvas->m_plane.draw_fill();
tex.unbind();
}
#endif
}
void CanvasModePen::leave()
{
m_brush = canvas->m_current_brush;
}
void CanvasModePen::enter()
{
m_cur_pos = ui::Canvas::I->m_cur_pos;
if (m_valid_brush)
{
canvas->m_current_brush = m_brush;
App::I.brush_update();
}
else
{
m_brush = canvas->m_current_brush;
m_valid_brush = true;
}
}
////////////////////////////////////////////////////////////////////
void CanvasModeLine::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
switch (me->m_type)
{
case kEventType::MouseDownL:
node->mouse_capture();
m_dragging = true;
m_drag_start = loc;
m_drag_pos = loc;
break;
case kEventType::MouseUpL:
node->mouse_release();
if (m_dragging)
{
canvas->stroke_start(m_drag_start, 1.f, canvas->m_current_brush);
canvas->stroke_update(m_drag_pos, 1.f);
canvas->stroke_end();
}
m_dragging = false;
break;
case kEventType::MouseMove:
if (m_dragging)
m_drag_pos = loc;
break;
case kEventType::MouseCancel:
node->mouse_release();
m_dragging = false;
break;
default:
break;
}
}
void CanvasModeLine::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
if (m_dragging)
{
ui::ShaderManager::use(ui::kShader::Color);
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP, ortho);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, canvas->m_current_brush.m_tip_color);
static glm::vec4 AB[2];
AB[0] = { m_drag_start, 0, 1 };
AB[1] = { m_drag_pos, 0, 1 };
AB[0].y = canvas->m_box.w - AB[0].y - 1; // invert Y
AB[1].y = canvas->m_box.w - AB[1].y - 1; // invert Y
m_line.update_vertices(AB);
m_line.draw_stroke();
}
}
void CanvasModeLine::init()
{
m_line.create();
}
////////////////////////////////////////////////////////////////////
void CanvasModeCamera::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
switch (me->m_type)
{
case kEventType::MouseDownR:
canvas->m_cam_pos = { 0, 0, 0 };
break;
case kEventType::MouseDownL:
m_dragging = true;
m_drag_start = me->m_pos;
m_pos_start = xy(canvas->m_cam_pos);
node->mouse_capture();
break;
case kEventType::MouseUpL:
m_dragging = false;
node->mouse_release();
canvas->m_cam_pos = { 0, 0, 0 };
break;
case kEventType::MouseMove:
if (m_dragging)
canvas->m_cam_pos = glm::vec3(m_pos_start + (me->m_pos - m_drag_start) * glm::vec2(1, -1) * 0.001f, canvas->m_cam_pos.z);
break;
case kEventType::MouseCancel:
m_dragging = false;
node->mouse_release();
break;
default:
break;
}
}
////////////////////////////////////////////////////////////////////
void CanvasModeGrid::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
switch (me->m_type)
{
case kEventType::MouseDownL:
{
node->mouse_capture();
glm::vec3 ro, rd, hit_o, hit_d;
glm::vec2 fb_pos;
if (canvas->point_trace(loc, ro, rd, hit_o, fb_pos, hit_d, m_plane_id))
{
m_lines.push_back({ hit_o, hit_d });
origin = hit_o;
dir = hit_d;
m_dragging = true;
}
break;
}
case kEventType::MouseUpL:
node->mouse_release();
m_dragging = false;
//commit();
break;
case kEventType::MouseMove:
{
glm::vec3 ro, rd, hit_o, hit_d;
glm::vec2 hit_fb;
if (m_dragging && canvas->point_trace_plane(loc, ro, rd, hit_o, hit_d, hit_fb, m_plane_id))
{
m_lines.back() = { hit_o, hit_d };
origin = hit_o;
dir = hit_d;
m_dragging = true;
}
break;
}
case kEventType::MouseCancel:
if (m_dragging)
m_lines.pop_back();
m_dragging = false;
node->mouse_release();
break;
default:
break;
}
}
void CanvasModeGrid::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
//if (m_dragging)
for (auto l : m_lines)
{
auto origin = l.o;
auto dir = l.d;
ui::ShaderManager::use(ui::kShader::Color);
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP, proj * camera);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, {1, 0, 0, 1});
static glm::vec4 AB[2];
AB[0] = {origin - dir * 10.f, 1};
AB[1] = {origin + dir * 10.f, 1 };
m_line.update_vertices(AB);
m_line.draw_stroke();
}
}
void CanvasModeGrid::init()
{
m_line.create();
}
void CanvasModeGrid::commit()
{
auto drawer = [this](const glm::mat4& camera, const glm::mat4& proj){
ui::ShaderManager::use(ui::kShader::Color);
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP, proj * camera);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, {1, 0, 0, 1});
static glm::vec4 AB[2];
AB[0] = {origin - dir * 10.f, 1};
AB[1] = {origin + dir * 10.f, 1 };
m_line.update_vertices(AB);
m_line.draw_stroke();
};
canvas->draw_objects(std::bind(drawer, std::placeholders::_1, std::placeholders::_2));
}
void CanvasModeGrid::clear()
{
m_lines.clear();
}
////////////////////////////////////////////////////////////////////
void CanvasModeMaskFree::init()
{
m_shape.create();
}
void CanvasModeMaskFree::leave()
{
// canvas->draw_objects(std::bind(&CanvasModeFill::on_Draw, this, glm::mat4(), std::placeholders::_1, std::placeholders::_2));
// m_points.clear();
}
void CanvasModeMaskFree::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
static glm::vec2 oldpos;
static glm::vec2 oldvec;
static float acc = 0.f;
switch (me->m_type)
{
case kEventType::MouseDownL:
{
node->mouse_capture();
m_dragging = true;
m_points2d.clear();
m_points.clear();
oldpos = loc;
oldvec = {1.f, 0.f};
acc = 0;
ui::Shape::vertex_t vert;
vert.pos = glm::vec4(loc, 0, 1);
m_points2d.push_back(loc);
m_points2d.push_back(loc);
m_points.push_back(vert);
m_points.push_back(vert);
canvas->m_smask.clear({0, 0, 0, 0});
canvas->m_smask_active = true;
break;
}
case kEventType::MouseUpL:
node->mouse_release();
m_dragging = false;
if (m_points2d.size() > 3)
{
std::vector<std::shared_ptr<p2t::Point>> points;
for (int i = 0; i < (int)m_points2d.size() - 1; i++)
points.emplace_back(std::make_shared<p2t::Point>(m_points2d[i].x, m_points2d[i].y));
auto v = canvas->triangulate(points);
canvas->project2Dpoints(v);
LOG("%d points", (int)v.size());
m_shape.update_vertices(v.data(), (int)v.size());
if (!m_points.empty())
{
auto drawer = [this](const glm::mat4& camera, const glm::mat4& proj) {
//glEnable(GL_BLEND);
ui::ShaderManager::use(ui::kShader::Color);
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP, proj * camera);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, {1, 1, 1, 1});
m_shape.draw_fill();
};
canvas->draw_objects(std::bind(drawer, std::placeholders::_1, std::placeholders::_2), canvas->m_smask);
//m_points.clear();
// close the path
m_points.push_back(m_points[m_points.size() - 2]);
m_points.push_back(m_points.front());
canvas->project2Dpoints(m_points);
// reset m_shape to contour rendering
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
}
else
{
canvas->m_smask_active = false;
}
break;
case kEventType::MouseMove:
{
if (m_dragging)
{
auto v = loc-oldpos;
float len = glm::length(v);
if (len > 5)
{
m_points.back().pos = glm::vec4(loc, 0, 1);
m_points2d.back() = loc;
v = glm::normalize(v);
float d = 1-glm::dot(v, oldvec);
acc += d;
oldpos = loc;
oldvec = v;
if (acc > 0.001) // angle change tollerance
{
//LOG("d=%f acc=%f", d, acc);
acc = 0;
m_points2d.push_back(loc);
ui::Shape::vertex_t vert;
vert.pos = glm::vec4(loc, 0, 1);
m_points.push_back(vert);
m_points.push_back(vert);
}
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
}
break;
}
case kEventType::MouseCancel:
if (m_dragging)
{
m_points.pop_back();
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
m_dragging = false;
node->mouse_release();
if (m_points.size() < 4)
{
m_points.clear();
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
break;
default:
break;
}
}
void CanvasModeMaskFree::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
if (m_points.size() > 3)
{
if (m_dragging)
{
ui::ShaderManager::use(ui::kShader::Color);
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP, glm::scale(glm::vec3(1,-1,1)) * ortho);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, { 0, 0, 0, 1 });
//m_dragging ? m_shape.draw_stroke() : m_shape.draw_fill();
m_shape.draw_stroke();
}
else
{
ui::ShaderManager::use(ui::kShader::Color);
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP, proj * camera);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, { 0, 0, 0, 1 });
m_shape.draw_stroke();
}
}
}
////////////////////////////////////////////////////////////////////
void CanvasModeMaskLine::init()
{
m_shape.create();
}
void CanvasModeMaskLine::leave()
{
if (m_points2d.size() > 3)
{
std::vector<std::shared_ptr<p2t::Point>> points;
for (int i = 0; i < (int)m_points2d.size(); i++)
points.emplace_back(std::make_shared<p2t::Point>(m_points2d[i].x, m_points2d[i].y));
auto v = canvas->triangulate(points);
canvas->project2Dpoints(v);
LOG("%d points", (int)v.size());
m_shape.update_vertices(v.data(), (int)v.size());
if (!m_points.empty())
{
auto drawer = [this](const glm::mat4& camera, const glm::mat4& proj) {
//glEnable(GL_BLEND);
ui::ShaderManager::use(ui::kShader::Color);
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP, proj * camera);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, {1, 1, 1, 1});
m_shape.draw_fill();
};
canvas->draw_objects(std::bind(drawer, std::placeholders::_1, std::placeholders::_2), canvas->m_smask);
//m_points.clear();
// close the path
m_points.push_back(m_points.back());
m_points.push_back(m_points.back());
m_points.push_back(m_points.front());
canvas->project2Dpoints(m_points);
// reset m_shape to contour rendering
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
}
else
{
canvas->m_smask_active = false;
}
m_active_tool = false;
}
void CanvasModeMaskLine::enter()
{
m_points2d.clear();
m_points.clear();
canvas->m_smask.clear({0, 0, 0, 0});
canvas->m_smask_active = true;
m_active_tool = true;
}
void CanvasModeMaskLine::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
switch (me->m_type)
{
case kEventType::MouseDownL:
{
node->mouse_capture();
m_dragging = true;
m_points2d.push_back(loc);
ui::Shape::vertex_t vert;
vert.pos = glm::vec4(loc, 0, 1);
m_points.push_back(vert);
m_shape.update_vertices(m_points.data(), (int)m_points.size());
break;
}
case kEventType::MouseUpL:
node->mouse_release();
m_dragging = false;
if (m_points.size() > 1)
{
m_points.push_back(m_points.back());
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
break;
case kEventType::MouseMove:
{
if (m_dragging)
{
m_points.back().pos = glm::vec4(loc, 0, 1);
m_points2d.back() = loc;
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
break;
}
case kEventType::MouseCancel:
if (m_dragging)
{
m_points.pop_back();
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
m_dragging = false;
node->mouse_release();
if (m_points.size() < 4)
{
m_points.clear();
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
break;
default:
break;
}
}
void CanvasModeMaskLine::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
if (m_points.size() > 3)
{
if (m_active_tool)
{
ui::ShaderManager::use(ui::kShader::Color);
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP, glm::scale(glm::vec3(1,-1,1)) * ortho);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, { 0, 0, 0, 1 });
//m_dragging ? m_shape.draw_stroke() : m_shape.draw_fill();
m_shape.draw_stroke();
}
else
{
ui::ShaderManager::use(ui::kShader::Color);
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP, proj * camera);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, { 0, 0, 0, 1 });
m_shape.draw_stroke();
}
}
}
////////////////////////////////////////////////////////////////////
void CanvasModeFill::init()
{
m_shape.create();
}
void CanvasModeFill::leave()
{
if (m_points.size() > 2)
{
auto drawer = [this](const glm::mat4& camera, const glm::mat4& proj) {
ui::ShaderManager::use(ui::kShader::Color);
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP, proj * camera);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, {1, 1, 1, 1});
m_shape.draw_fill();
};
canvas->draw_objects(std::bind(drawer, std::placeholders::_1, std::placeholders::_2), canvas->m_smask);
m_points.clear();
canvas->m_smask_active = true;
}
else
{
canvas->m_smask_active = false;
}
}
void CanvasModeFill::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
switch (me->m_type)
{
case kEventType::MouseDownL:
{
node->mouse_capture();
m_dragging = true;
glm::vec3 ro, rd, hit_o, hit_d;
glm::vec2 hit_fb;
int plane_id;
if (canvas->point_trace_plane(loc, ro, rd, hit_o, hit_d, hit_fb, 0))
{
m_dirty_planes[plane_id]++;
ui::Shape::vertex_t v;
v.pos = glm::vec4(hit_o, 1);
v.uvs = glm::vec2(0);
if (m_points.size() < 3)
{
m_points.push_back(v);
}
else
{
auto last = m_points.back();
m_points.push_back(m_points[0]);
m_points.push_back(last);
m_points.push_back(v);
}
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
canvas->m_smask.clear({0, 0, 0, 0});
break;
}
case kEventType::MouseUpL:
node->mouse_release();
m_dragging = false;
break;
case kEventType::MouseMove:
{
glm::vec3 ro, rd, hit_o, hit_d;
glm::vec2 fb_pos;
int plane_id;
if (m_dragging && canvas->point_trace(loc, ro, rd, hit_o, fb_pos, hit_d, plane_id))
{
ui::Shape::vertex_t v;
v.pos = glm::vec4(hit_o, 1);
v.uvs = glm::vec2(0);
m_points.back() = v;
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
break;
}
case kEventType::MouseCancel:
if (m_dragging)
{
m_points.pop_back();
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
m_dragging = false;
node->mouse_release();
if (m_points.size() < 4)
{
m_points.clear();
m_shape.update_vertices(m_points.data(), (int)m_points.size());
}
break;
default:
break;
}
}
void CanvasModeFill::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
if (!m_points.empty())
{
ui::ShaderManager::use(ui::kShader::Color);
ui::ShaderManager::u_mat4(ui::kShaderUniform::MVP, proj * camera);
ui::ShaderManager::u_vec4(ui::kShaderUniform::Col, { 0, 0, 0, .25 });
m_dragging ? m_shape.draw_fill() : m_shape.draw_stroke();
}
}

139
src/canvas_modes.h Normal file
View File

@@ -0,0 +1,139 @@
#pragma once
#include "event.h"
#include "shape.h"
#include "brush.h"
#include <poly2tri.h>
NS_START
class Canvas;
NS_END
class CanvasMode
{
public:
static class NodeCanvas* node;
static ui::Canvas* canvas;
virtual void on_MouseEvent(MouseEvent* me, glm::vec2& loc) {}
virtual void on_KeyEvent(KeyEvent* ke) {}
virtual void on_GestureEvent(GestureEvent* ge) {}
virtual void on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera) {}
virtual void init() {}
virtual void enter() {}
virtual void leave() {}
};
class CanvasModeBasicCamera : public CanvasMode
{
bool m_draggingR = false;
glm::vec2 m_dragR_start;
glm::vec2 m_pan_start;
float m_camera_fov;
float m_zoom_canvas = 1.f;
float m_zoom_start;
public:
virtual void on_MouseEvent(MouseEvent* me, glm::vec2& loc) override;
virtual void on_GestureEvent(GestureEvent* ge) override;
};
class CanvasModePen : public CanvasMode
{
bool m_dragging = false;
glm::vec2 m_pan_start;
glm::vec2 m_cur_pos;
glm::vec2 m_size_pos_start;
float m_size_value_start;
float m_camera_fov;
float m_zoom_canvas = 1.f;
float m_zoom_start;
bool m_valid_brush = false;
ui::Brush m_brush;
// resizing the tip
bool m_resizing = false;
public:
virtual void on_MouseEvent(MouseEvent* me, glm::vec2& loc) override;
virtual void on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera) override;
virtual void enter() override;
virtual void leave() override;
bool m_picking = false;
};
class CanvasModeLine : public CanvasMode
{
ui::LineSegment m_line;
bool m_dragging = false;
glm::vec2 m_drag_start;
glm::vec2 m_drag_pos;
public:
virtual void on_MouseEvent(MouseEvent* me, glm::vec2& loc) override;
virtual void on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera) override;
virtual void init() override;
};
class CanvasModeGrid : public CanvasMode
{
ui::LineSegment m_line;
glm::vec3 origin;
glm::vec3 dir;
int m_plane_id;
bool m_dragging = false;
struct ray_t { glm::vec3 o, d; };
std::vector<ray_t> m_lines;
public:
virtual void on_MouseEvent(MouseEvent* me, glm::vec2& loc) override;
virtual void on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera) override;
virtual void init() override;
void commit();
void clear();
};
class CanvasModeCamera : public CanvasMode
{
bool m_dragging = false;
glm::vec2 m_drag_start;
glm::vec2 m_pos_start;
public:
virtual void on_MouseEvent(MouseEvent* me, glm::vec2& loc) override;
};
class CanvasModeFill : public CanvasMode
{
ui::DynamicShape m_shape;
bool m_dragging = false;
std::vector<ui::Shape::vertex_t> m_points;
std::map<int, int> m_dirty_planes;
public:
virtual void on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera) override;
virtual void on_MouseEvent(MouseEvent* me, glm::vec2& loc) override;
virtual void init() override;
virtual void leave() override;
};
class CanvasModeMaskFree : public CanvasMode
{
ui::DynamicShape m_shape;
bool m_dragging = false;
std::vector<ui::Shape::vertex_t> m_points;
std::vector<glm::vec2> m_points2d;
std::map<int, int> m_dirty_planes;
public:
virtual void on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera) override;
virtual void on_MouseEvent(MouseEvent* me, glm::vec2& loc) override;
virtual void init() override;
virtual void leave() override;
};
class CanvasModeMaskLine : public CanvasMode
{
ui::DynamicShape m_shape;
bool m_dragging = false;
std::vector<ui::Shape::vertex_t> m_points;
std::vector<glm::vec2> m_points2d;
std::map<int, int> m_dirty_planes;
bool m_active_tool = false;
public:
virtual void on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera) override;
virtual void on_MouseEvent(MouseEvent* me, glm::vec2& loc) override;
virtual void init() override;
virtual void enter() override;
virtual void leave() override;
};

3
src/event.cpp Normal file
View File

@@ -0,0 +1,3 @@
#include "pch.h"
#include "event.h"

154
src/event.h Normal file
View File

@@ -0,0 +1,154 @@
#pragma once
enum class kKey : uint8_t
{
Unknown,
AndroidVolumeUp,
AndroidVolumeDown,
AndroidHome,
AndroidBack,
KeySpacebar,
KeyA,
KeyB,
KeyC,
KeyD,
KeyE,
KeyF,
KeyG,
KeyH,
KeyI,
KeyJ,
KeyK,
KeyL,
KeyM,
KeyN,
KeyO,
KeyP,
KeyQ,
KeyR,
KeyS,
KeyT,
KeyU,
KeyV,
KeyW,
KeyX,
KeyY,
KeyZ,
Key0,
Key1,
Key2,
Key3,
Key4,
Key5,
Key6,
Key7,
Key8,
Key9,
KeyF1,
KeyF2,
KeyF3,
KeyF4,
KeyF5,
KeyF6,
KeyF7,
KeyF8,
KeyF9,
KeyF10,
KeyF11,
KeyF12,
KeyF13,
KeyF14,
KeyF15,
KeyF16,
KeyF17,
KeyF18,
KeyF19,
KeyF20,
KeyF21,
KeyF22,
KeyF23,
KeyF24,
KeyAlt,
KeyCtrl,
KeyShift,
KeyTab,
};
enum class kEventResult : uint8_t
{
Consumed,
Available,
};
enum class kEventCategory : uint8_t
{
MouseEvent,
KeyEvent,
ButtonEvent,
GestureEvent,
};
enum class kEventType : uint8_t
{
MouseDownL,
MouseDownR,
MouseMove,
MouseUpL,
MouseUpR,
MouseEnter,
MouseLeave,
MouseScroll,
MouseCancel,
GestureStart,
GestureMove,
GestureEnd,
KeyDown,
KeyUp,
KeyChar,
ButtonDown,
ButtonUp,
};
enum class kEventSource : uint8_t
{
Mouse,
Touch,
Stylus,
};
class Event
{
public:
kEventCategory m_cat;
kEventType m_type;
};
class MouseEvent : public Event
{
public:
MouseEvent() { m_cat = kEventCategory::MouseEvent; }
glm::vec2 m_pos;
float m_pressure = 0;
float m_scroll_delta = 0;
kEventSource m_source = kEventSource::Mouse;
};
class KeyEvent : public Event
{
public:
KeyEvent() { m_cat = kEventCategory::KeyEvent; }
kKey m_key;
char m_char;
};
class GestureEvent : public Event
{
public:
GestureEvent() { m_cat = kEventCategory::GestureEvent; }
float m_distance;
float m_distance_delta;
float m_angle;
float m_angle_delta;
glm::vec2 m_pos;
glm::vec2 m_pos_delta;
};

136
src/font.cpp Normal file
View File

@@ -0,0 +1,136 @@
#include "pch.h"
#include "log.h"
#include "font.h"
#include "shader.h"
#include "asset.h"
std::map<kFont, Font> FontManager::m_fonts;
Sampler FontManager::m_sampler;
bool Font::load(const char* ttf, int font_size)
{
Asset file;
LOG("Font::load %s", ttf);
if (file.open(ttf) && file.read_all())
{
LOG("Font::load loaded");
auto bitmap = std::make_unique<uint8_t[]>(w*h);
chars.resize(num_chars);
stbtt_BakeFontBitmap(file.m_data, 0, (float)font_size*2, bitmap.get(), w, h, start_char, num_chars, chars.data());
font_tex.create(w, h, GL_R8, GL_RED, bitmap.get());
file.close();
return true;
}
return false;
}
void FontManager::init()
{
m_sampler.create();
}
bool FontManager::load(kFont id, const char* ttf, int sz)
{
return m_fonts[id].load(ttf, sz);
}
const Font& FontManager::get(kFont id)
{
return m_fonts[id];
}
bool TextMesh::create()
{
glGenBuffers(2, font_buffers);
#if USE_VBO
glGenVertexArrays(1, &font_array);
glBindVertexArray(font_array);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_buffers[1]);
glBindBuffer(GL_ARRAY_BUFFER, font_buffers[0]);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (GLvoid*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (GLvoid*)(sizeof(float)*2));
glBindVertexArray(0);
#endif // USE_VBO
return true;
}
void TextMesh::update(kFont id, const char* text)
{
font_id = id;
auto& f = FontManager::get(id);
if (f.chars.size())
{
const auto len = strlen(text);
float x = 0;
float y = 0;
std::vector<glm::vec4> v;
std::vector<GLushort> idx;
glm::vec2 bbmin(FLT_MAX);
glm::vec2 bbmax(-FLT_MAX);
for (int i = 0; i < len; i++)
{
int c = text[i] - f.start_char;
stbtt_aligned_quad q;
stbtt_GetBakedQuad((stbtt_bakedchar*)f.chars.data(), f.w, f.h, c, &x, &y, &q, true);
auto n = (int)v.size();
v.emplace_back(q.x0/2.f, q.y1/2.f, q.s0, q.t1);
v.emplace_back(q.x0/2.f, q.y0/2.f, q.s0, q.t0);
v.emplace_back(q.x1/2.f, q.y0/2.f, q.s1, q.t0);
v.emplace_back(q.x1/2.f, q.y1/2.f, q.s1, q.t1);
idx.push_back(n+0);
idx.push_back(n+1);
idx.push_back(n+2);
idx.push_back(n+0);
idx.push_back(n+2);
idx.push_back(n+3);
bbmin = glm::min(bbmin, { q.x0/2.f, q.y0/2.f });
bbmax = glm::max(bbmax, { q.x1/2.f, q.y1/2.f });
}
for (int i = 0; i < len*4; i++)
{
v[i] -= glm::vec4(bbmin, 0, 0);
}
bb = bbmax - bbmin;
font_array_count = (int)idx.size();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_buffers[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx.size() * sizeof(GLushort), idx.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, font_buffers[0]);
glBufferData(GL_ARRAY_BUFFER, v.size() * sizeof(glm::vec4), v.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
}
void TextMesh::draw()
{
auto& f = FontManager::get(font_id);
if (f.font_tex.ready())
{
glActiveTexture(GL_TEXTURE0);
f.font_tex.bind();
FontManager::m_sampler.bind(0);
#if USE_VBO
glBindVertexArray(font_array);
glDrawElements(GL_TRIANGLES, font_array_count, GL_UNSIGNED_SHORT, 0);
glBindVertexArray(0);
#else
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_buffers[1]);
glBindBuffer(GL_ARRAY_BUFFER, font_buffers[0]);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (GLvoid*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (GLvoid*)(sizeof(float) * 2));
glDrawElements(GL_TRIANGLES, font_array_count, GL_UNSIGNED_SHORT, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
#endif // USE_VBO
f.font_tex.unbind();
FontManager::m_sampler.unbind();
}
}

48
src/font.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include "texture.h"
#include "util.h"
#include <stb/stb_truetype.h>
enum class kFont : uint16_t
{
Arial_11 = const_hash("arial-11"),
Arial_30 = const_hash("arial-30"),
};
class Font
{
public:
const int w = 512;
const int h = 512;
const int num_chars = 96;
const int start_char = 32;
stbtt_fontinfo font;
Texture2D font_tex;
std::vector<stbtt_bakedchar> chars;
bool load(const char* ttf, int sz);
};
class FontManager
{
public:
static std::map<kFont, Font> m_fonts;
static Sampler m_sampler;
static void init();
static bool load(kFont id, const char* ttf, int sz);
static const Font& get(kFont id);
static void invalidate() { m_fonts.clear(); }
};
class TextMesh
{
public:
GLuint font_array = 0;
int font_array_count = 0;
GLuint font_buffers[2] = {0, 0};
kFont font_id;
glm::vec2 bb = { 0, 0 };
bool create();
void update(kFont id, const char* text);
void draw();
};

80
src/image.cpp Normal file
View File

@@ -0,0 +1,80 @@
#include "pch.h"
#include "log.h"
#include "image.h"
#include "asset.h"
#include <stb/stb_image.h>
using namespace ui;
bool Image::load(std::string filename)
{
stbi_set_flip_vertically_on_load(false);
Asset file;
if (!(file.open(filename.c_str()) && file.read_all()))
{
file.close();
return false;
}
uint8_t* buffer = stbi_load_from_memory(file.m_data, file.m_len, &width, &height, nullptr, 4);
file.close();
comp = 4;
m_data = std::unique_ptr<uint8_t[]>(buffer);
return true;
}
bool Image::load_file(std::string filename)
{
stbi_set_flip_vertically_on_load(false);
uint8_t* buffer = stbi_load(filename.c_str(), &width, &height, nullptr, 4);
comp = 4;
m_data = std::unique_ptr<uint8_t[]>(buffer);
return true;
}
void Image::flip()
{
auto flipped = std::make_unique<uint8_t[]>(width*height*4);
int line_size = width * 4;
const uint8_t* src = m_data.get();
uint8_t* dst = flipped.get() + line_size * (height - 1);
for (int y = 0; y < height; y++)
{
std::copy(src, src+line_size, dst);
src += line_size;
dst -= line_size;
}
std::swap(m_data, flipped);
}
ui::Image ui::Image::resize(int w, int h)
{
Image ret;
ret.create(w, h);
auto temp = (glm::u8vec4*)ret.data();
auto pixels = (glm::u8vec4*)data();
float x_ratio = ((float)(width - 1)) / w;
float y_ratio = ((float)(height - 1)) / h;
int offset = 0;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
int x = (int)(x_ratio * j);
int y = (int)(y_ratio * i);
float x_diff = (x_ratio * j) - x;
float y_diff = (y_ratio * i) - y;
int index = y * width + x;
// range is 0 to 255 thus bitwise AND with 0xff
glm::vec4 A = pixels[index];
glm::vec4 B = pixels[index + 1];
glm::vec4 C = pixels[index + width];
glm::vec4 D = pixels[index + width + 1];
// Y = A(1-w)(1-h) + B(w)(1-h) + C(h)(1-w) + Dwh
glm::vec4 gray = A*(1 - x_diff)*(1 - y_diff) + B * (x_diff)*(1 - y_diff) +
C * (y_diff)*(1 - x_diff) + D * (x_diff*y_diff);
temp[offset++] = glm::clamp(gray, glm::vec4(0), glm::vec4(255));
}
}
return ret;
}

32
src/image.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
namespace ui {
class Image
{
public:
std::unique_ptr<uint8_t[]> m_data;
int width = 0;
int height = 0;
int comp = 4;
bool load(std::string filename);
bool load_file(std::string filename);
const uint8_t* data() const { return m_data.get(); }
int size() const { return width * height * comp; }
void create(int w, int h)
{
width = w;
height = h;
comp = 4;
m_data = std::make_unique<uint8_t[]>(size());
}
void copy_from(const uint8_t* data)
{
std::copy(data, data + size(), m_data.get());
}
void flip();
void create() { m_data = std::make_unique<uint8_t[]>(size()); }
Image resize(int w, int h);
};
}

439
src/keymap.h Normal file
View File

@@ -0,0 +1,439 @@
enum {
kVK_ANSI_A = 0x00,
kVK_ANSI_S = 0x01,
kVK_ANSI_D = 0x02,
kVK_ANSI_F = 0x03,
kVK_ANSI_H = 0x04,
kVK_ANSI_G = 0x05,
kVK_ANSI_Z = 0x06,
kVK_ANSI_X = 0x07,
kVK_ANSI_C = 0x08,
kVK_ANSI_V = 0x09,
kVK_ANSI_B = 0x0B,
kVK_ANSI_Q = 0x0C,
kVK_ANSI_W = 0x0D,
kVK_ANSI_E = 0x0E,
kVK_ANSI_R = 0x0F,
kVK_ANSI_Y = 0x10,
kVK_ANSI_T = 0x11,
kVK_ANSI_1 = 0x12,
kVK_ANSI_2 = 0x13,
kVK_ANSI_3 = 0x14,
kVK_ANSI_4 = 0x15,
kVK_ANSI_6 = 0x16,
kVK_ANSI_5 = 0x17,
kVK_ANSI_Equal = 0x18,
kVK_ANSI_9 = 0x19,
kVK_ANSI_7 = 0x1A,
kVK_ANSI_Minus = 0x1B,
kVK_ANSI_8 = 0x1C,
kVK_ANSI_0 = 0x1D,
kVK_ANSI_RightBracket = 0x1E,
kVK_ANSI_O = 0x1F,
kVK_ANSI_U = 0x20,
kVK_ANSI_LeftBracket = 0x21,
kVK_ANSI_I = 0x22,
kVK_ANSI_P = 0x23,
kVK_ANSI_L = 0x25,
kVK_ANSI_J = 0x26,
kVK_ANSI_Quote = 0x27,
kVK_ANSI_K = 0x28,
kVK_ANSI_Semicolon = 0x29,
kVK_ANSI_Backslash = 0x2A,
kVK_ANSI_Comma = 0x2B,
kVK_ANSI_Slash = 0x2C,
kVK_ANSI_N = 0x2D,
kVK_ANSI_M = 0x2E,
kVK_ANSI_Period = 0x2F,
kVK_ANSI_Grave = 0x32,
kVK_ANSI_KeypadDecimal = 0x41,
kVK_ANSI_KeypadMultiply = 0x43,
kVK_ANSI_KeypadPlus = 0x45,
kVK_ANSI_KeypadClear = 0x47,
kVK_ANSI_KeypadDivide = 0x4B,
kVK_ANSI_KeypadEnter = 0x4C,
kVK_ANSI_KeypadMinus = 0x4E,
kVK_ANSI_KeypadEquals = 0x51,
kVK_ANSI_Keypad0 = 0x52,
kVK_ANSI_Keypad1 = 0x53,
kVK_ANSI_Keypad2 = 0x54,
kVK_ANSI_Keypad3 = 0x55,
kVK_ANSI_Keypad4 = 0x56,
kVK_ANSI_Keypad5 = 0x57,
kVK_ANSI_Keypad6 = 0x58,
kVK_ANSI_Keypad7 = 0x59,
kVK_ANSI_Keypad8 = 0x5B,
kVK_ANSI_Keypad9 = 0x5C
};
/* keycodes for keys that are independent of keyboard layout*/
enum {
kVK_Return = 0x24,
kVK_Tab = 0x30,
kVK_Space = 0x31,
kVK_Delete = 0x33,
kVK_Escape = 0x35,
kVK_Command = 0x37,
kVK_Shift = 0x38,
kVK_CapsLock = 0x39,
kVK_Option = 0x3A,
kVK_Control = 0x3B,
kVK_RightShift = 0x3C,
kVK_RightOption = 0x3D,
kVK_RightControl = 0x3E,
kVK_Function = 0x3F,
kVK_F17 = 0x40,
kVK_VolumeUp = 0x48,
kVK_VolumeDown = 0x49,
kVK_Mute = 0x4A,
kVK_F18 = 0x4F,
kVK_F19 = 0x50,
kVK_F20 = 0x5A,
kVK_F5 = 0x60,
kVK_F6 = 0x61,
kVK_F7 = 0x62,
kVK_F3 = 0x63,
kVK_F8 = 0x64,
kVK_F9 = 0x65,
kVK_F11 = 0x67,
kVK_F13 = 0x69,
kVK_F16 = 0x6A,
kVK_F14 = 0x6B,
kVK_F10 = 0x6D,
kVK_F12 = 0x6F,
kVK_F15 = 0x71,
kVK_Help = 0x72,
kVK_Home = 0x73,
kVK_PageUp = 0x74,
kVK_ForwardDelete = 0x75,
kVK_F4 = 0x76,
kVK_End = 0x77,
kVK_F2 = 0x78,
kVK_PageDown = 0x79,
kVK_F1 = 0x7A,
kVK_LeftArrow = 0x7B,
kVK_RightArrow = 0x7C,
kVK_DownArrow = 0x7D,
kVK_UpArrow = 0x7E
};
kKey convert_key(int key)
{
#define CASE(K,V) case K: return V;
switch(key)
{
#ifdef __APPLE__
CASE(kVK_ANSI_A, kKey::KeyA);
CASE(kVK_ANSI_S, kKey::KeyS);
CASE(kVK_ANSI_D, kKey::KeyD);
CASE(kVK_ANSI_F, kKey::KeyF);
CASE(kVK_ANSI_H, kKey::KeyH);
CASE(kVK_ANSI_G, kKey::KeyG);
CASE(kVK_ANSI_Z, kKey::KeyZ);
CASE(kVK_ANSI_X, kKey::KeyX);
CASE(kVK_ANSI_C, kKey::KeyC);
CASE(kVK_ANSI_V, kKey::KeyV);
CASE(kVK_ANSI_B, kKey::KeyB);
CASE(kVK_ANSI_Q, kKey::KeyQ);
CASE(kVK_ANSI_W, kKey::KeyW);
CASE(kVK_ANSI_E, kKey::KeyE);
CASE(kVK_ANSI_R, kKey::KeyR);
CASE(kVK_ANSI_Y, kKey::KeyY);
CASE(kVK_ANSI_T, kKey::KeyT);
CASE(kVK_ANSI_1, kKey::Key1);
CASE(kVK_ANSI_2, kKey::Key2);
CASE(kVK_ANSI_3, kKey::Key3);
CASE(kVK_ANSI_4, kKey::Key4);
CASE(kVK_ANSI_6, kKey::Key6);
CASE(kVK_ANSI_5, kKey::Key5);
CASE(kVK_ANSI_Equal, kKey::Unknown);
CASE(kVK_ANSI_9, kKey::Key9);
CASE(kVK_ANSI_7, kKey::Key7);
CASE(kVK_ANSI_Minus, kKey::Unknown);
CASE(kVK_ANSI_8, kKey::Key8);
CASE(kVK_ANSI_0, kKey::Key0);
CASE(kVK_ANSI_RightBracket, kKey::Unknown);
CASE(kVK_ANSI_O, kKey::KeyO);
CASE(kVK_ANSI_U, kKey::KeyU);
CASE(kVK_ANSI_LeftBracket, kKey::Unknown);
CASE(kVK_ANSI_I, kKey::KeyI);
CASE(kVK_ANSI_P, kKey::KeyP);
CASE(kVK_ANSI_L, kKey::KeyL);
CASE(kVK_ANSI_J, kKey::KeyJ);
CASE(kVK_ANSI_Quote, kKey::Unknown);
CASE(kVK_ANSI_K, kKey::KeyK);
CASE(kVK_ANSI_Semicolon, kKey::Unknown);
CASE(kVK_ANSI_Backslash, kKey::Unknown);
CASE(kVK_ANSI_Comma, kKey::Unknown);
CASE(kVK_ANSI_Slash, kKey::Unknown);
CASE(kVK_ANSI_N, kKey::KeyN);
CASE(kVK_ANSI_M, kKey::KeyM);
CASE(kVK_ANSI_Period, kKey::Unknown);
CASE(kVK_ANSI_Grave, kKey::Unknown);
CASE(kVK_ANSI_KeypadDecimal, kKey::Unknown);
CASE(kVK_ANSI_KeypadMultiply, kKey::Unknown);
CASE(kVK_ANSI_KeypadPlus, kKey::Unknown);
CASE(kVK_ANSI_KeypadClear, kKey::Unknown);
CASE(kVK_ANSI_KeypadDivide, kKey::Unknown);
CASE(kVK_ANSI_KeypadEnter, kKey::Unknown);
CASE(kVK_ANSI_KeypadMinus, kKey::Unknown);
CASE(kVK_ANSI_KeypadEquals, kKey::Unknown);
CASE(kVK_ANSI_Keypad0, kKey::Unknown);
CASE(kVK_ANSI_Keypad1, kKey::Unknown);
CASE(kVK_ANSI_Keypad2, kKey::Unknown);
CASE(kVK_ANSI_Keypad3, kKey::Unknown);
CASE(kVK_ANSI_Keypad4, kKey::Unknown);
CASE(kVK_ANSI_Keypad5, kKey::Unknown);
CASE(kVK_ANSI_Keypad6, kKey::Unknown);
CASE(kVK_ANSI_Keypad7, kKey::Unknown);
CASE(kVK_ANSI_Keypad8, kKey::Unknown);
CASE(kVK_ANSI_Keypad9, kKey::Unknown);
CASE(kVK_Return, kKey::Unknown);
CASE(kVK_Tab, kKey::KeyTab);
CASE(kVK_Space, kKey::Unknown);
CASE(kVK_Delete, kKey::Unknown);
CASE(kVK_Escape, kKey::Unknown);
CASE(kVK_Command, kKey::KeyCtrl);
CASE(kVK_Shift, kKey::KeyShift);
CASE(kVK_CapsLock, kKey::Unknown);
CASE(kVK_Option, kKey::Unknown);
CASE(kVK_Control, kKey::Unknown);
CASE(kVK_RightShift, kKey::Unknown);
CASE(kVK_RightOption, kKey::Unknown);
CASE(kVK_RightControl, kKey::Unknown);
CASE(kVK_Function, kKey::Unknown);
CASE(kVK_F17, kKey::Unknown);
CASE(kVK_VolumeUp, kKey::Unknown);
CASE(kVK_VolumeDown, kKey::Unknown);
CASE(kVK_Mute, kKey::Unknown);
CASE(kVK_F18, kKey::KeyF18);
CASE(kVK_F19, kKey::KeyF19);
CASE(kVK_F20, kKey::KeyF20);
CASE(kVK_F5, kKey::KeyF5);
CASE(kVK_F6, kKey::KeyF6);
CASE(kVK_F7, kKey::KeyF7);
CASE(kVK_F3, kKey::KeyF3);
CASE(kVK_F8, kKey::KeyF8);
CASE(kVK_F9, kKey::KeyF9);
CASE(kVK_F11, kKey::KeyF11);
CASE(kVK_F13, kKey::KeyF13);
CASE(kVK_F16, kKey::KeyF16);
CASE(kVK_F14, kKey::KeyF14);
CASE(kVK_F10, kKey::KeyF10);
CASE(kVK_F12, kKey::KeyF12);
CASE(kVK_F15, kKey::KeyF15);
CASE(kVK_Help, kKey::Unknown);
CASE(kVK_Home, kKey::Unknown);
CASE(kVK_PageUp, kKey::Unknown);
CASE(kVK_ForwardDelete, kKey::Unknown);
CASE(kVK_F4, kKey::Unknown);
CASE(kVK_End, kKey::Unknown);
CASE(kVK_F2, kKey::Unknown);
CASE(kVK_PageDown, kKey::Unknown);
CASE(kVK_F1, kKey::Unknown);
CASE(kVK_LeftArrow, kKey::Unknown);
CASE(kVK_RightArrow, kKey::Unknown);
CASE(kVK_DownArrow, kKey::Unknown);
CASE(kVK_UpArrow, kKey::Unknown);
#elif defined(_WIN32)
CASE(VK_LBUTTON, kKey::Unknown);
CASE(VK_RBUTTON, kKey::Unknown);
CASE(VK_CANCEL, kKey::Unknown);
CASE(VK_MBUTTON, kKey::Unknown);
CASE(VK_XBUTTON1, kKey::Unknown);
CASE(VK_XBUTTON2, kKey::Unknown);
CASE(VK_BACK, kKey::Unknown);
CASE(VK_TAB, kKey::KeyTab);
CASE(VK_CLEAR, kKey::Unknown);
CASE(VK_RETURN, kKey::Unknown);
CASE(VK_SHIFT, kKey::KeyShift);
CASE(VK_CONTROL, kKey::KeyCtrl);
CASE(VK_MENU, kKey::KeyAlt);
CASE(VK_PAUSE, kKey::Unknown);
CASE(VK_CAPITAL, kKey::Unknown);
CASE(VK_KANA, kKey::Unknown);
//CASE(VK_HANGEUL, kKey::Unknown); // same as VK_KANA
//CASE(VK_HANGUL, kKey::Unknown); // same as VK_KANA
CASE(VK_JUNJA, kKey::Unknown);
CASE(VK_FINAL, kKey::Unknown);
CASE(VK_HANJA, kKey::Unknown);
//CASE(VK_KANJI, kKey::Unknown); // same as VK_HANJA
CASE(VK_ESCAPE, kKey::Unknown);
CASE(VK_CONVERT, kKey::Unknown);
CASE(VK_NONCONVERT, kKey::Unknown);
CASE(VK_ACCEPT, kKey::Unknown);
CASE(VK_MODECHANGE, kKey::Unknown);
CASE(VK_SPACE, kKey::KeySpacebar);
CASE(VK_PRIOR, kKey::Unknown);
CASE(VK_NEXT, kKey::Unknown);
CASE(VK_END, kKey::Unknown);
CASE(VK_HOME, kKey::Unknown);
CASE(VK_LEFT, kKey::Unknown);
CASE(VK_UP, kKey::Unknown);
CASE(VK_RIGHT, kKey::Unknown);
CASE(VK_DOWN, kKey::Unknown);
CASE(VK_SELECT, kKey::Unknown);
CASE(VK_PRINT, kKey::Unknown);
CASE(VK_EXECUTE, kKey::Unknown);
CASE(VK_SNAPSHOT, kKey::Unknown);
CASE(VK_INSERT, kKey::Unknown);
CASE(VK_DELETE, kKey::Unknown);
CASE(VK_HELP, kKey::Unknown);
CASE('0', kKey::Key0);
CASE('1', kKey::Key1);
CASE('2', kKey::Key2);
CASE('3', kKey::Key3);
CASE('4', kKey::Key4);
CASE('5', kKey::Key5);
CASE('6', kKey::Key6);
CASE('7', kKey::Key7);
CASE('8', kKey::Key8);
CASE('9', kKey::Key9);
CASE('A', kKey::KeyA);
CASE('B', kKey::KeyB);
CASE('C', kKey::KeyC);
CASE('D', kKey::KeyD);
CASE('E', kKey::KeyE);
CASE('F', kKey::KeyF);
CASE('G', kKey::KeyG);
CASE('H', kKey::KeyH);
CASE('I', kKey::KeyI);
CASE('J', kKey::KeyJ);
CASE('K', kKey::KeyK);
CASE('L', kKey::KeyL);
CASE('M', kKey::KeyM);
CASE('N', kKey::KeyN);
CASE('O', kKey::KeyO);
CASE('P', kKey::KeyP);
CASE('Q', kKey::KeyQ);
CASE('R', kKey::KeyR);
CASE('S', kKey::KeyS);
CASE('T', kKey::KeyT);
CASE('U', kKey::KeyU);
CASE('V', kKey::KeyV);
CASE('W', kKey::KeyW);
CASE('X', kKey::KeyX);
CASE('Y', kKey::KeyY);
CASE('Z', kKey::KeyZ);
CASE(VK_LWIN, kKey::Unknown);
CASE(VK_RWIN, kKey::Unknown);
CASE(VK_APPS, kKey::Unknown);
CASE(VK_SLEEP, kKey::Unknown);
CASE(VK_NUMPAD0, kKey::Unknown);
CASE(VK_NUMPAD1, kKey::Unknown);
CASE(VK_NUMPAD2, kKey::Unknown);
CASE(VK_NUMPAD3, kKey::Unknown);
CASE(VK_NUMPAD4, kKey::Unknown);
CASE(VK_NUMPAD5, kKey::Unknown);
CASE(VK_NUMPAD6, kKey::Unknown);
CASE(VK_NUMPAD7, kKey::Unknown);
CASE(VK_NUMPAD8, kKey::Unknown);
CASE(VK_NUMPAD9, kKey::Unknown);
CASE(VK_MULTIPLY, kKey::Unknown);
CASE(VK_ADD, kKey::Unknown);
CASE(VK_SEPARATOR, kKey::Unknown);
CASE(VK_SUBTRACT, kKey::Unknown);
CASE(VK_DECIMAL, kKey::Unknown);
CASE(VK_DIVIDE, kKey::Unknown);
CASE(VK_F1, kKey::KeyF1);
CASE(VK_F2, kKey::KeyF2);
CASE(VK_F3, kKey::KeyF3);
CASE(VK_F4, kKey::KeyF4);
CASE(VK_F5, kKey::KeyF5);
CASE(VK_F6, kKey::KeyF6);
CASE(VK_F7, kKey::KeyF7);
CASE(VK_F8, kKey::KeyF8);
CASE(VK_F9, kKey::KeyF9);
CASE(VK_F10, kKey::KeyF10);
CASE(VK_F11, kKey::KeyF11);
CASE(VK_F12, kKey::KeyF12);
CASE(VK_F13, kKey::KeyF13);
CASE(VK_F14, kKey::KeyF14);
CASE(VK_F15, kKey::KeyF15);
CASE(VK_F16, kKey::KeyF16);
CASE(VK_F17, kKey::KeyF17);
CASE(VK_F18, kKey::KeyF18);
CASE(VK_F19, kKey::KeyF19);
CASE(VK_F20, kKey::KeyF20);
CASE(VK_F21, kKey::KeyF21);
CASE(VK_F22, kKey::KeyF22);
CASE(VK_F23, kKey::KeyF23);
CASE(VK_F24, kKey::KeyF24);
CASE(VK_NUMLOCK, kKey::Unknown);
CASE(VK_SCROLL, kKey::Unknown);
//CASE(VK_OEM_NEC_EQUAL, kKey::Unknown); // same as VK_OEM_FJ_JISHO
CASE(VK_OEM_FJ_JISHO, kKey::Unknown);
CASE(VK_OEM_FJ_MASSHOU, kKey::Unknown);
CASE(VK_OEM_FJ_TOUROKU, kKey::Unknown);
CASE(VK_OEM_FJ_LOYA, kKey::Unknown);
CASE(VK_OEM_FJ_ROYA, kKey::Unknown);
CASE(VK_LSHIFT, kKey::Unknown);
CASE(VK_RSHIFT, kKey::Unknown);
CASE(VK_LCONTROL, kKey::Unknown);
CASE(VK_RCONTROL, kKey::Unknown);
CASE(VK_LMENU, kKey::Unknown);
CASE(VK_RMENU, kKey::Unknown);
CASE(VK_BROWSER_BACK, kKey::Unknown);
CASE(VK_BROWSER_FORWARD, kKey::Unknown);
CASE(VK_BROWSER_REFRESH, kKey::Unknown);
CASE(VK_BROWSER_STOP, kKey::Unknown);
CASE(VK_BROWSER_SEARCH, kKey::Unknown);
CASE(VK_BROWSER_FAVORITES, kKey::Unknown);
CASE(VK_BROWSER_HOME, kKey::Unknown);
CASE(VK_VOLUME_MUTE, kKey::Unknown);
CASE(VK_VOLUME_DOWN, kKey::Unknown);
CASE(VK_VOLUME_UP, kKey::Unknown);
CASE(VK_MEDIA_NEXT_TRACK, kKey::Unknown);
CASE(VK_MEDIA_PREV_TRACK, kKey::Unknown);
CASE(VK_MEDIA_STOP, kKey::Unknown);
CASE(VK_MEDIA_PLAY_PAUSE, kKey::Unknown);
CASE(VK_LAUNCH_MAIL, kKey::Unknown);
CASE(VK_LAUNCH_MEDIA_SELECT, kKey::Unknown);
CASE(VK_LAUNCH_APP1, kKey::Unknown);
CASE(VK_LAUNCH_APP2, kKey::Unknown);
CASE(VK_OEM_1, kKey::Unknown);
CASE(VK_OEM_PLUS, kKey::Unknown);
CASE(VK_OEM_COMMA, kKey::Unknown);
CASE(VK_OEM_MINUS, kKey::Unknown);
CASE(VK_OEM_PERIOD, kKey::Unknown);
CASE(VK_OEM_2, kKey::Unknown);
CASE(VK_OEM_3, kKey::Unknown);
CASE(VK_OEM_4, kKey::Unknown);
CASE(VK_OEM_5, kKey::Unknown);
CASE(VK_OEM_6, kKey::Unknown);
CASE(VK_OEM_7, kKey::Unknown);
CASE(VK_OEM_8, kKey::Unknown);
CASE(VK_OEM_AX, kKey::Unknown);
CASE(VK_OEM_102, kKey::Unknown);
CASE(VK_ICO_HELP, kKey::Unknown);
CASE(VK_ICO_00, kKey::Unknown);
CASE(VK_PROCESSKEY, kKey::Unknown);
CASE(VK_ICO_CLEAR, kKey::Unknown);
CASE(VK_PACKET, kKey::Unknown);
CASE(VK_OEM_RESET, kKey::Unknown);
CASE(VK_OEM_JUMP, kKey::Unknown);
CASE(VK_OEM_PA1, kKey::Unknown);
CASE(VK_OEM_PA2, kKey::Unknown);
CASE(VK_OEM_PA3, kKey::Unknown);
CASE(VK_OEM_WSCTRL, kKey::Unknown);
CASE(VK_OEM_CUSEL, kKey::Unknown);
CASE(VK_OEM_ATTN, kKey::Unknown);
CASE(VK_OEM_FINISH, kKey::Unknown);
CASE(VK_OEM_COPY, kKey::Unknown);
CASE(VK_OEM_AUTO, kKey::Unknown);
CASE(VK_OEM_ENLW, kKey::Unknown);
CASE(VK_OEM_BACKTAB, kKey::Unknown);
CASE(VK_ATTN, kKey::Unknown);
CASE(VK_CRSEL, kKey::Unknown);
CASE(VK_EXSEL, kKey::Unknown);
CASE(VK_EREOF, kKey::Unknown);
CASE(VK_PLAY, kKey::Unknown);
CASE(VK_ZOOM, kKey::Unknown);
CASE(VK_NONAME, kKey::Unknown);
CASE(VK_PA1, kKey::Unknown);
CASE(VK_OEM_CLEAR, kKey::Unknown);
#endif
default:
return kKey::Unknown;
}
}

106
src/layout.cpp Normal file
View File

@@ -0,0 +1,106 @@
#include "pch.h"
#include "log.h"
#include "layout.h"
#include "util.h"
#include "asset.h"
#include "node.h"
#include "node_border.h"
bool LayoutManager::load(const char* path)
{
auto abs_path = Asset::absolute(path);
#if _WIN32 || __OSX__
struct stat tmp_info;
if (stat(abs_path.c_str(), &tmp_info) != 0)
return false;
if (tmp_info.st_mtime <= m_file_info.st_mtime)
return false;
m_file_info = tmp_info;
#else
if (m_loaded)
return true; // already loaded
#endif // __ANDROID__
m_path = path;
auto old = std::move(m_layouts);
Asset file;
if (!(file.open(path) && file.read_all()))
return false;
tinyxml2::XMLDocument xml;
auto ret = xml.Parse((char*)file.m_data, file.m_len);
file.close();
if (ret != tinyxml2::XMLError::XML_SUCCESS)
{
return false;
LOG("parsing xml failed");
}
LOG("parsing loaded xml");
tinyxml2::XMLElement* current = xml.RootElement()->FirstChildElement();
while (current)
{
auto id_str = current->Attribute("id");
if (!id_str)
{
LOG("Layout node without id");
return false;
}
//LOG("Parsing layout: %s", id_str);
uint16_t id = const_hash(id_str);
auto p = m_layouts.find(id);
if (p == m_layouts.end())
{
auto& node = m_layouts[id];
kWidget node_id = (kWidget)const_hash(current->Name());
switch (node_id)
{
case kWidget::Border:
node.reset(new NodeBorder());
break;
default:
node.reset(new Node());
break;
}
node->m_manager = this;
// try to copy the old size values
if (old.count(id))
{
const auto& old_node = *old[id];
YGNodeCopyStyle(node->y_node, old_node.y_node);
}
node->load_internal(current);
}
else
{
LOG("Layout id \"%s\" duplicated", id_str);
}
current = current->NextSiblingElement("layout");
}
if (on_loaded)
on_loaded();
m_loaded = true;
return true;
}
bool LayoutManager::reload()
{
// avoid conflict when assigning the same string from c_str
std::string path_copy = m_path;
return load(path_copy.c_str());
}
void LayoutManager::restore_context()
{
for (auto& node : m_layouts)
node.second->restore_context();
}
void LayoutManager::clear_context()
{
for (auto& node : m_layouts)
node.second->clear_context();
}

37
src/layout.h Normal file
View File

@@ -0,0 +1,37 @@
#pragma once
#include "shape.h"
#include "util.h"
#include "shader.h"
#include "font.h"
#include "asset.h"
#include "rtt.h"
#include "bezier.h"
#include "canvas.h"
#include "event.h"
#include <tinyxml2.h>
#include <yoga/Yoga.h>
class LayoutManager
{
std::map<uint16_t, std::unique_ptr<class Node>> m_layouts;
std::string m_path;
struct stat m_file_info { 0 };
public:
bool m_loaded = false;
std::function<void()> on_loaded;
bool load(const char* path);
bool reload();
class Node* operator[](uint16_t id)
{
auto i = m_layouts.find(id);
return i == m_layouts.end() ? nullptr : i->second.get();
}
class Node* get(uint16_t id)
{
auto i = m_layouts.find(id);
return i == m_layouts.end() ? nullptr : i->second.get();
}
void restore_context();
void clear_context();
//Node& operator[](const char* ids) { return m_layouts[const_hash(ids)]; }
};

133
src/log.cpp Normal file
View File

@@ -0,0 +1,133 @@
#include "pch.h"
#include "log.h"
#include "app.h"
LogRemote LogRemote::I;
void LogRemote::start()
{
if (m_running || m_error)
return; // already running
m_running = true;
m_thread = std::thread([&] {
#ifdef _WIN32
BT_SetTerminate();
#endif
net_init();
auto session_string = net_request("/start");
m_session = atoi(session_string.c_str());
while (m_running && !m_error)
{
auto m = m_mq.Get();
auto escaped = curl_easy_escape(curl, m.c_str(), (int)m.size());
auto data = std::make_unique<char[]>(m.size() + 64);
int sz = snprintf(data.get(), m.size() + 64, "session=%d&m=%s", m_session, escaped);
curl_free(escaped);
net_request("/log", std::string(data.get(), sz));
}
net_close();
LOG("NET thread loop exit");
});
}
void LogRemote::stop()
{
m_running = false;
m_mq.UnlockGetters();
if (m_thread.joinable())
m_thread.join();
}
void LogRemote::net_init()
{
if (!(curl = curl_easy_init()))
return;
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &this->readBuffer);
//curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
}
std::string LogRemote::net_request(std::string cmd, std::string data /*= ""*/)
{
readBuffer.clear();
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
auto url = m_url + cmd;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
res = curl_easy_perform(curl);
if (res != CURLcode::CURLE_OK)
{
LOG("NET error, closed");
m_running = false;
m_error = true;
}
return readBuffer;
}
void LogRemote::net_close()
{
if (curl)
curl_easy_cleanup(curl);
curl = nullptr;
m_running = false;
}
void LogRemote::file_init()
{
if (!m_logfile.is_open())
m_logfile.open(App::I.data_path + "/panopainter-log.txt");
}
void LogRemote::file_close()
{
if (!m_logfile.is_open())
m_logfile.close();
}
void LogRemote::log(const char* format, ...)
{
static char buffer[4096];
va_list arglist;
va_start(arglist, format);
int n = vsnprintf(buffer, sizeof(buffer), format, arglist);
va_end(arglist);
m_mq.Post(std::string(buffer, n));
if (m_logfile.is_open())
{
auto line = std::string(buffer, n) + "\n";
m_logfile.write(line.data(), line.size());
m_logfile.flush();
}
}
void LogRemote::log(const wchar_t* format, ...)
{
static wchar_t buffer[4096];
va_list arglist;
va_start(arglist, format);
int n = vswprintf(buffer, sizeof(buffer)/sizeof(wchar_t), format, arglist);
va_end(arglist);
std::wstring string_to_convert(buffer, n);
//setup converter
// using convert_type = std::codecvt_utf8<wchar_t>;
// std::wstring_convert<convert_type, wchar_t> converter;
mbstate_t st = {};
std::string converted;
converted.reserve(string_to_convert.size());
const wchar_t * wptr = string_to_convert.c_str();
std::wcsrtombs((char*)converted.data(), &wptr, converted.capacity(), &st);
//use converter (.to_bytes: wstr->str, .from_bytes: str->wstr)
//std::string converted_str = converter.to_bytes(string_to_convert);
m_mq.Post(std::move(converted));
if (m_logfile.is_open())
{
auto line = converted + "\n";
m_logfile.write(line.data(), line.size());
m_logfile.flush();
}
}
LogRemote::~LogRemote()
{
stop();
}

38
src/log.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include "util.h"
#ifdef __APPLE__
#define LOG(M,...) { printf(M"\n", ##__VA_ARGS__); LogRemote::I.log(M, ##__VA_ARGS__); }
#elif __ANDROID__
#define LOG(...) { ((void)__android_log_print(ANDROID_LOG_INFO, "native-engine", __VA_ARGS__)); LogRemote::I.log(__VA_ARGS__); }
#elif _WIN32
#define LOG(M,...) { printf(M"\n", ##__VA_ARGS__); LogRemote::I.log(M, ##__VA_ARGS__); }
#define LOGW(M,...) { wprintf(M"\n", ##__VA_ARGS__); LogRemote::I.log(M, ##__VA_ARGS__); }
#endif
class LogRemote
{
public:
static LogRemote I;
bool m_running = false;
bool m_error = false;
std::thread m_thread;
ui::BlockingQueue<std::string> m_mq;
CURL *curl = nullptr;
CURLcode res;
std::string readBuffer;
std::string m_url = "http://omigamedev.ddns.net:8083";
int m_session;
std::ofstream m_logfile;
void start();
void stop();
void net_init();
std::string net_request(std::string cmd, std::string data = "");
void net_close();
void file_init();
void file_close();
void log(const char* format, ...);
void log(const wchar_t* format, ...);
~LogRemote();
};

767
src/main.cpp Normal file
View File

@@ -0,0 +1,767 @@
#include "pch.h"
#include "log.h"
#include "shader.h"
#include "shape.h"
#include "texture.h"
#include "image.h"
#include "app.h"
#include "keymap.h"
#include "../resource.h"
#include <WbemCli.h>
#include "wacom.h"
#include <deque>
#include <chrono>
#pragma comment (lib, "opengl32.lib")
#pragma comment (lib, "glew32.lib")
#pragma comment(lib, "wbemuuid.lib")
#define WM_USER_CLOSE (WM_USER + 1)
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);
HINSTANCE hInst;
HWND hWnd;
HDC hDC;
HGLRC hRC;
wchar_t* className;
bool keys[256];
std::mutex gl_mutex;
std::mutex async_mutex;
std::thread::id gl_thread;
int gl_count = 0;
std::deque<std::packaged_task<void()>> tasklist;
std::mutex task_mutex;
//Returns the last Win32 error, in string format. Returns an empty string if there is no error.
std::string GetLastErrorAsString()
{
//Get the error message, if any.
DWORD errorMessageID = ::GetLastError();
if (errorMessageID == 0)
return std::string(); //No error message has been recorded
LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
std::string message(messageBuffer, size);
//Free the buffer.
LocalFree(messageBuffer);
return message;
}
void destroy_window()
{
SendMessage(hWnd, WM_USER_CLOSE, 0, 0);
}
void async_lock()
{
//std::lock_guard<std::mutex> _lock(async_mutex);
if (gl_count == 0 || gl_thread != std::this_thread::get_id())
{
gl_mutex.lock();
bool ret = wglMakeCurrent(hDC, hRC);
if (ret == false)
LOG("FAILED wglMakeCurrent: %s", GetLastErrorAsString().c_str());
gl_thread = std::this_thread::get_id();
//LOG("lock");
}
gl_count++;
//assert(ret == true);
}
bool async_lock_try()
{
//std::lock_guard<std::mutex> _lock(async_mutex);
if (gl_count == 0 || gl_thread != std::this_thread::get_id())
{
if (!gl_mutex.try_lock())
return false;
bool ret = wglMakeCurrent(hDC, hRC);
if (ret == false)
LOG("FAILED wglMakeCurrent: %s", GetLastErrorAsString().c_str());
gl_thread = std::this_thread::get_id();
//LOG("lock");
}
gl_count++;
//assert(ret == true);
return true;
}
void async_swap()
{
SwapBuffers(hDC);
//LOG("swap");
}
void async_unlock()
{
//std::lock_guard<std::mutex> _lock(async_mutex);
gl_count--;
if (gl_count == 0)
{
//LOG("unlock");
wglMakeCurrent(0, 0);
gl_mutex.unlock();
}
}
std::string win32_open_file()
{
OPENFILENAMEA ofn;
char fileName[MAX_PATH] = "";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hWnd;
ofn.lpstrFilter = "Image Files (*.jpg, *.png)\0*.jpg;*.png";
ofn.lpstrFile = fileName;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
ofn.lpstrDefExt = "";
ofn.lpstrInitialDir = "";
if (GetOpenFileNameA(&ofn) != NULL)
{
return fileName;
}
return "";
}
struct async_locker
{
async_locker() { async_lock(); }
~async_locker() { async_unlock(); }
};
int read_WMI_info()
{
// see: http://win32easy.blogspot.co.uk/2011/03/wmi-in-c-query-everyting-from-your-os.html
HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hRes))
{
LOG("Unable to launch COM: %x", hRes);
return 1;
}
if ((FAILED(hRes = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0))))
{
LOG("Unable to initialize security: %x", hRes);
return 1;
}
IWbemLocator* pLocator = NULL;
if (FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pLocator))))
{
LOG("Unable to create a WbemLocator: %x", hRes);
return 1;
}
IWbemServices* pService = NULL;
if (FAILED(hRes = pLocator->ConnectServer(L"root\\CIMV2", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService)))
{
pLocator->Release();
LOG("Unable to connect to \"CIMV2\": %x", hRes);
return 1;
}
auto log_field = [](const wchar_t* section, IWbemClassObject* clsObj, const wchar_t* field) {
VARIANT vRet;
VariantInit(&vRet);
if (SUCCEEDED(clsObj->Get(field, 0, &vRet, NULL, NULL)) && vRet.vt == VT_BSTR)
{
LOGW(L"%s %s: %s", section, field, vRet.bstrVal);
VariantClear(&vRet);
}
};
// GET DEVICE INFO
{
IEnumWbemClassObject* pEnumerator = NULL;
if (FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_ComputerSystem", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator)))
{
pLocator->Release();
pService->Release();
LOG("Unable to retrive desktop monitors: %x", hRes);
return 1;
}
IWbemClassObject* clsObj = NULL;
int numElems;
while ((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE)
{
if (FAILED(hRes))
break;
log_field(L"Machine", clsObj, L"Name");
log_field(L"Machine", clsObj, L"Model");
log_field(L"Machine", clsObj, L"Manufacturer");
clsObj->Release();
}
pEnumerator->Release();
}
// GET OS INFO
{
IEnumWbemClassObject* pEnumerator = NULL;
if (FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_OperatingSystem", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator)))
{
pLocator->Release();
pService->Release();
LOG("Unable to retrive desktop monitors: %x", hRes);
return 1;
}
IWbemClassObject* clsObj = NULL;
int numElems;
while ((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE)
{
if (FAILED(hRes))
break;
log_field(L"OS", clsObj, L"Name");
log_field(L"OS", clsObj, L"Version");
log_field(L"OS", clsObj, L"Locale");
log_field(L"OS", clsObj, L"OSProductSuite");
log_field(L"OS", clsObj, L"Manufacturer");
log_field(L"OS", clsObj, L"Description");
clsObj->Release();
}
pEnumerator->Release();
}
pService->Release();
pLocator->Release();
CoUninitialize();
return 0;
}
INT_PTR g_iLogHandle = -1;
static void SetupExceptionHandler()
{
// Setup exception handler
BT_SetAppName(_T("PanoPainter"));
//BT_SetSupportEMail(_T("your@email.com"));
BT_SetFlags(BTF_DETAILEDMODE | BTF_ATTACHREPORT | BTF_SCREENCAPTURE);
// = BugTrapServer ===========================================
BT_SetSupportServer(_T("omigamedev.ddns.net"), 8088);
// - or -
//BT_SetSupportServer(_T("127.0.0.1"), 9999);
// = BugTrapWebServer ========================================
//BT_SetSupportServer(_T("http://localhost/BugTrapWebServer/RequestHandler.aspx"), BUGTRAP_HTTP_PORT);
//BT_SetSupportServer(_T("http://omigamedev.ddns.net:8088/source/Server/BugTrapWebServer/RequestHandler.aspx"), BUGTRAP_HTTP_PORT);
// required for VS 2005 & 2008
BT_InstallSehFilter();
// Add custom log file using default name
// g_iLogHandle = BT_OpenLogFile(NULL, BTLF_TEXT);
// BT_SetLogSizeInEntries(g_iLogHandle, 100);
// BT_SetLogFlags(g_iLogHandle, BTLF_SHOWTIMESTAMP);
// BT_SetLogEchoMode(g_iLogHandle, BTLE_STDERR | BTLE_DBGOUT);
//
// PCTSTR pszLogFileName = BT_GetLogFileName(g_iLogHandle);
TCHAR wpath[1024];
GetFullPathNameW(L"panopainter-log.txt", 1024, wpath, nullptr);
BT_AddLogFile(wpath);
BT_SetPreErrHandler([](INT_PTR){
if (ui::Canvas::I)
{
auto path = App::I.data_path + "/recovery.pano";
ui::Canvas::I->project_save_thread(path);
static char abspath[MAX_PATH];
GetFullPathNameA(path.c_str(), MAX_PATH, abspath, NULL);
static char message[4096];
snprintf(message, sizeof(message), "File recovered in: %s", abspath);
MessageBoxA(NULL, message, "File Recovery", MB_OK | MB_ICONWARNING);
}
LogRemote::I.file_close();
}, 0);
}
int main(int argc, char** argv)
{
WNDCLASS wc;
PIXELFORMATDESCRIPTOR pfd;
if (argc == 1)
App::I.initLog();
if (!App::I.check_license())
{
MessageBoxA(NULL, "Unable to verify this demo license, please make sure you are connected to Internet.",
"PanoPainter - License Error", MB_ICONERROR | MB_OK);
return -1;
}
SetupExceptionHandler();
BT_SetTerminate();
read_WMI_info();
App::I.create();
RECT clientRect = { 0, 0, (int)App::I.width, (int)App::I.height };
// Inizialize data structures to zero
memset(&wc, 0, sizeof(wc));
memset(&keys, 0, sizeof(keys));
memset(&pfd, 0, sizeof(pfd));
// Create the main window
hInst = GetModuleHandle(NULL);
className = L"EngineMain";
wc.hInstance = hInst;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.lpszClassName = className;
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
AdjustWindowRect(&clientRect, WS_OVERLAPPEDWINDOW, false);
hWnd = CreateWindow(wc.lpszClassName, L"PanoPainter", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, clientRect.right - clientRect.left,
clientRect.bottom - clientRect.top, 0, 0, hInst, 0);
// Setup GL Rendering Context
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 16;
pfd.iLayerType = PFD_MAIN_PLANE;
hDC = GetDC(hWnd);
int pxfmt = ChoosePixelFormat(hDC, &pfd);
SetPixelFormat(hDC, pxfmt, &pfd);
hRC = wglCreateContext(hDC); // Create OpenGL 2.1 or less
wglMakeCurrent(hDC, hRC);
// Initialize extensions
if (glewInit() != GLEW_OK)
return 0;
LOG("GL version: %s", glGetString(GL_VERSION));
LOG("GL vendor: %s", glGetString(GL_VENDOR));
LOG("GL renderer: %s", glGetString(GL_RENDERER));
static wchar_t window_title[512];
swprintf_s(window_title, L"PanoPainter %s", g_version_number_w);
// If supported create a 3.1 context
if (wglewIsSupported("WGL_ARB_create_context"))
{
int contex_attribs[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 1,
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};
int pixel_attribs[] =
{
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, 32,
WGL_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
WGL_SAMPLE_BUFFERS_ARB, 1, // Number of buffers (must be 1 at time of writing)
WGL_SAMPLES_ARB, 4, // Number of samples
0
};
UINT numFormat;
wglMakeCurrent(NULL, NULL);
wglDeleteContext(hRC);
DestroyWindow(hWnd);
hWnd = CreateWindow(wc.lpszClassName, window_title, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, clientRect.right - clientRect.left,
clientRect.bottom - clientRect.top, 0, 0, hInst, 0);
hDC = GetDC(hWnd);
wglChoosePixelFormatARB(hDC, pixel_attribs, nullptr, 1, &pxfmt, &numFormat);
SetPixelFormat(hDC, pxfmt, &pfd);
hRC = wglCreateContextAttribsARB(hDC, NULL, contex_attribs);
wglMakeCurrent(hDC, hRC);
}
else
{
// If not supported, go fuck yourself we are not gonna support your shitty device
return -1; // A negative number because you are a negative one
}
if (argc > 1)
{
switch (const_hash(argv[1]))
{
case const_hash("convert"):
App::I.initShaders();
App::I.cmd_convert(argv[2], argv[3]);
return 0;
default:
break;
}
}
App::I.init();
ShowWindow(hWnd, SW_NORMAL);
WacomTablet::I.init(hWnd);
SendMessage(hWnd, WM_SETICON, ICON_SMALL,
(LPARAM)LoadIcon(GetModuleHandle(0), MAKEINTRESOURCE(IDI_ICON1)));
SetTimer(hWnd, 1, 500, [](HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
App::I.redraw = true;
});
bool running = true;
std::mutex render_mutex;
std::condition_variable render_cv;
std::thread renderer([&] {
BT_SetTerminate();
const float target_fps = 10;
unsigned long t0 = GetTickCount();
unsigned long t1;
int frames = 0;
float one_sec = 0;
float render_timer = 0;
while(running)
{
t1 = GetTickCount();
float dt = (float)(t1 - t0) / 1000.0f;
one_sec += dt;
render_timer += dt;
t0 = t1;
if (one_sec > 1.f)
{
static wchar_t title_fps[512];
swprintf_s(title_fps, L"%s - %d fps", window_title, frames);
SetWindowText(hWnd, title_fps);
one_sec = 0;
frames = 0;
}
{
std::deque<std::packaged_task<void()>> working_list;
{
std::lock_guard<std::mutex> lock(task_mutex);
working_list = std::move(tasklist);
}
async_lock();
while (!working_list.empty())
{
working_list.front()();
working_list.pop_front();
}
async_unlock();
}
std::unique_lock<std::mutex> lock(render_mutex);
if (render_timer > 1.0f / target_fps || App::I.redraw)
{
render_timer = 0;
if (App::I.redraw)
{
async_lock();
App::I.redraw = true;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
App::I.clear();
App::I.update(dt);
SwapBuffers(hDC);
async_unlock();
//LOG("swap main");
}
}
frames++;
const int framerate = (1.f / target_fps) * 1000;
const int diff = framerate - (t1 - t0);
render_cv.wait_for(lock, std::chrono::milliseconds(diff));
}
});
MSG msg;
while (running)
{
// If there any message in the queue process it
auto present = App::I.animate || App::I.redraw ?
PeekMessage(&msg, 0, 0, 0, PM_REMOVE) : GetMessage(&msg, 0, 0, 0);
running = !(msg.message == WM_QUIT/* || gl.keys[VK_ESCAPE]*/);
if (present)
{
DispatchMessage(&msg);
TranslateMessage(&msg);
}
if (!tasklist.empty())
render_cv.notify_all();
}
render_cv.notify_all();
if (renderer.joinable())
renderer.join();
App::I.terminate();
// Clean up
WacomTablet::I.terminate();
//DestroyWindow(hWnd);
UnregisterClass(className, hInst);
LogRemote::I.stop();
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
static bool leftDown = false;
static DWORD lastTime;
static POINT lastPoint;
switch (msg)
{
case WM_USER_CLOSE:
DestroyWindow(hWnd);
PostQuitMessage(0);
break;
case WM_PAINT:
App::I.redraw = true;
break;
case WM_CREATE:
BT_SetTerminate();
break;
case WM_CLOSE:
if (App::I.request_close())
PostQuitMessage(0);
else
return true;
break;
case WM_SIZE:
{
auto w = (float)LOWORD(lp);
auto h = (float)HIWORD(lp);
if (h != 0)
{
async_locker lock;
App::I.resize(w, h);
App::I.clear();
App::I.redraw = true;
App::I.update(0.f);
SwapBuffers(hDC);
}
break;
}
case WM_ACTIVATE:
{
int active = GET_WM_ACTIVATE_STATE(wp, lp);
WacomTablet::I.set_focus(active);
break;
}
case WT_PACKET:
WacomTablet::I.handle_message(hWnd, msg, wp, lp);
break;
case WM_SYSKEYDOWN:
case WM_KEYDOWN:
{
std::lock_guard<std::mutex> lock(task_mutex);
tasklist.emplace_back([wp] {
App::I.key_down(convert_key((int)wp));
});
}
break;
case WM_SYSKEYUP:
case WM_KEYUP:
{
std::lock_guard<std::mutex> lock(task_mutex);
tasklist.emplace_back([wp] {
App::I.key_up(convert_key((int)wp));
});
}
break;
case WM_CHAR:
{
std::lock_guard<std::mutex> lock(task_mutex);
tasklist.emplace_back([wp]{
App::I.key_char((int)wp);
});
}
break;
case WM_MOUSEMOVE:
{
std::lock_guard<std::mutex> lock(task_mutex);
tasklist.emplace_back([lp, p = WacomTablet::I.get_pressure()]{
App::I.mouse_move((float)GET_X_LPARAM(lp), (float)GET_Y_LPARAM(lp), p, kEventSource::Mouse);
});
}
break;
case WM_LBUTTONDOWN:
SetCapture(hWnd);
{
std::lock_guard<std::mutex> lock(task_mutex);
tasklist.emplace_back([lp, p = WacomTablet::I.get_pressure()]{
App::I.mouse_down(0, (float)GET_X_LPARAM(lp), (float)GET_Y_LPARAM(lp), p, kEventSource::Mouse);
});
}
break;
case WM_LBUTTONUP:
WacomTablet::I.reset_pressure();
ReleaseCapture();
{
std::lock_guard<std::mutex> lock(task_mutex);
tasklist.emplace_back([lp]{
App::I.mouse_up(0, (float)GET_X_LPARAM(lp), (float)GET_Y_LPARAM(lp), kEventSource::Mouse);
});
}
break;
case WM_RBUTTONDOWN:
SetCapture(hWnd);
{
std::lock_guard<std::mutex> lock(task_mutex);
tasklist.emplace_back([lp]{
App::I.mouse_down(1, (float)GET_X_LPARAM(lp), (float)GET_Y_LPARAM(lp), 1.f, kEventSource::Mouse);
});
}
break;
case WM_RBUTTONUP:
ReleaseCapture();
{
std::lock_guard<std::mutex> lock(task_mutex);
tasklist.emplace_back([lp]{
App::I.mouse_up(1, (float)GET_X_LPARAM(lp), (float)GET_Y_LPARAM(lp), kEventSource::Mouse);
});
}
break;
case WM_MOUSEWHEEL:
{
async_locker lock;
POINT pt;
pt.x = GET_X_LPARAM(lp);
pt.y = GET_Y_LPARAM(lp);
ScreenToClient(hWnd, &pt);
{
std::lock_guard<std::mutex> lock(task_mutex);
tasklist.emplace_back([pt, wp] {
App::I.mouse_scroll((float)pt.x, (float)pt.y,
(float)GET_WHEEL_DELTA_WPARAM(wp) / (float)WHEEL_DELTA);
});
}
break;
}
case WM_POINTERUPDATE:
{
POINTER_TOUCH_INFO touchInfo;
POINTER_PEN_INFO penInfo;
POINTER_INFO pointerInfo;
UINT32 pointerId = GET_POINTERID_WPARAM(wp);
POINTER_INPUT_TYPE pointerType = PT_POINTER;
// Retrieve common pointer information
if (!GetPointerInfo(pointerId, &pointerInfo))
{
// failure, call GetLastError()
}
else
{
// success, process pointerInfo
if (!GetPointerType(pointerId, &pointerType))
{
// failure, call GetLastError()
// set PT_POINTER to fall to default case below
pointerType = PT_POINTER;
}
switch (pointerType)
{
case PT_TOUCH:
// Retrieve touch information
if (!GetPointerTouchInfo(pointerId, &touchInfo))
{
// failure, call GetLastError()
}
else
{
// success, process touchInfo
// mark as handled to skip call to DefWindowProc
//fHandled = TRUE;
}
break;
case PT_PEN:
// Retrieve pen information
if (!GetPointerPenInfo(pointerId, &penInfo))
{
// failure, call GetLastError()
}
else
{
// success, process penInfo
// mark as handled to skip call to DefWindowProc
//fHandled = TRUE;
//penInfo.pressure
}
break;
default:
if (!GetPointerInfo(pointerId, &pointerInfo))
{
// failure.
}
else
{
// success, proceed with pointerInfo.
//fHandled = HandleGenericPointerInfo(&pointerInfo);
}
break;
}
}
break;
}
}
// avoid annoying alt system menu
if (wp == SC_KEYMENU && (lp >> 16) <= 0) return 0;
return DefWindowProc(hWnd, msg, wp, lp);
}
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
LPWSTR *szArgList{ nullptr };
int argc{ 0 };
char** argv{ nullptr };
szArgList = CommandLineToArgvW(GetCommandLine(), &argc);
if (szArgList == NULL)
{
MessageBox(NULL, L"Unable to parse command line", L"Error", MB_OK);
return 10;
}
argv = new char*[argc + 1];
for (int i = 0; i < argc; i++)
{
auto len = wcslen(szArgList[i]) + 1;
argv[i] = new char[len];
wcstombs_s(nullptr, argv[i], len, szArgList[i], len);
}
LocalFree(szArgList);
return main(argc, argv);
}

994
src/node.cpp Normal file
View File

@@ -0,0 +1,994 @@
#include "pch.h"
#include "app.h"
#include "log.h"
#include "node.h"
#include "layout.h"
#include "util.h"
#include "asset.h"
#include "node_border.h"
#include "node_image.h"
#include "node_image_texture.h"
#include "node_icon.h"
#include "node_text.h"
#include "node_text_input.h"
#include "node_button.h"
#include "node_button_custom.h"
#include "node_slider.h"
#include "node_popup_menu.h"
#include "node_viewport.h"
#include "node_checkbox.h"
#include "node_panel_layer.h"
#include "node_panel_brush.h"
#include "node_panel_color.h"
#include "node_panel_stroke.h"
#include "node_color_quad.h"
#include "node_stroke_preview.h"
#include "node_canvas.h"
#include "node_scroll.h"
#include "node_dialog_browse.h"
#include "node_dialog_cloud.h"
#include "node_combobox.h"
#include "node_colorwheel.h"
#include "node_dialog_picker.h"
#include "node_panel_grid.h"
void Node::async_start()
{
App::I.async_start();
}
void Node::async_update()
{
App::I.async_update();
}
void Node::async_end()
{
App::I.async_end();
}
void Node::watch(std::function<bool(Node*)> observer)
{
bool cont = observer(this);
if (cont)
{
for (auto& c : m_children)
c->watch(observer);
}
}
void Node::destroy()
{
m_destroyed = true;
mouse_release();
key_release();
}
Node* Node::root()
{
Node* ret = this;
while (ret->parent)
ret = ret->parent;
return ret;
}
kEventResult Node::on_event(Event* e)
{
kEventResult ret = kEventResult::Available;
if (current_mouse_capture)
return current_mouse_capture->on_event(e);
bool skip_children = false;
skip_children |= (e->m_cat == kEventCategory::MouseEvent || e->m_cat == kEventCategory::GestureEvent) &&
(m_mouse_captured) && (root()->current_mouse_capture == this) && m_capture_children; // <-- THIS IS WRONG "!m_capture_children" is correct, but it breaks everything if changed
if (!m_display)
return kEventResult::Available;
if (!skip_children)
{
for (auto it = m_children.rbegin(); it != m_children.rend(); ++it)
{
if ((*it)->on_event(e) == kEventResult::Consumed)
{
if (m_flood_events)
{
ret = kEventResult::Consumed;
}
else
{
return kEventResult::Consumed;
}
}
}
if (ret == kEventResult::Consumed)
return ret;
}
switch (e->m_cat)
{
case kEventCategory::MouseEvent:
{
if (m_mouse_ignore)
break;
MouseEvent* me = static_cast<MouseEvent*>(e);
bool inside = point_in_rect(me->m_pos, m_clip);
bool inside_old = m_mouse_inside;
m_mouse_inside = inside;
switch (e->m_type)
{
case kEventType::MouseScroll:
case kEventType::MouseDownL:
case kEventType::MouseDownR:
case kEventType::MouseUpL:
case kEventType::MouseUpR:
if ((inside || m_mouse_captured) && handle_event(e) == kEventResult::Consumed)
return kEventResult::Consumed;
break;
case kEventType::MouseMove:
if (inside_old == false && inside == true)
{
MouseEvent e2 = *me;
e2.m_type = kEventType::MouseEnter;
handle_event(&e2);
}
if (inside || m_mouse_captured)
ret = handle_event(e);
if (inside_old == true && inside == false)
{
MouseEvent e2 = *me;
e2.m_type = kEventType::MouseLeave;
handle_event(&e2);
}
break;
default:
if (handle_event(e) == kEventResult::Consumed)
return kEventResult::Consumed;
break;
}
break;
}
case kEventCategory::GestureEvent:
{
if (m_mouse_ignore)
break;
GestureEvent* ge = static_cast<GestureEvent*>(e);
bool inside = point_in_rect(ge->m_pos, m_clip);
bool inside_old = m_mouse_inside;
m_mouse_inside = inside;
if ((inside || m_mouse_captured) && handle_event(e) == kEventResult::Consumed)
return kEventResult::Consumed;
break;
}
default:
if (handle_event(e) == kEventResult::Consumed)
return kEventResult::Consumed;
break;
}
return ret;
}
kEventResult Node::handle_event(Event* e)
{
return kEventResult::Available;
}
void Node::handle_resize(glm::vec2 old_size, glm::vec2 new_size)
{
}
void Node::create()
{
}
void Node::init()
{
}
void Node::loaded()
{
}
void Node::added(Node* parent)
{
}
void Node::removed(Node* parent)
{
}
const Node* Node::init_template(const char* id)
{
const auto& m_template = static_cast<Node*>((*m_manager)[const_hash(id)]->m_children[0].get());
for (auto& c : m_template->m_children)
{
auto node = c->clone();
add_child(node);
node->init();
node->create();
node->loaded();
}
YGNodeCopyStyle(y_node, m_template->y_node);
return m_template;
}
void Node::add_child(Node* n)
{
m_children.emplace_back(n);
n->parent = this;
n->m_manager = m_manager;
YGNodeInsertChild(y_node, n->y_node, YGNodeGetChildCount(y_node));
n->added(this);
}
void Node::add_child(Node* n, int index)
{
m_children.emplace_back(n);
n->parent = this;
n->m_manager = m_manager;
YGNodeInsertChild(y_node, n->y_node, index);
n->added(this);
}
void Node::add_child(std::shared_ptr<Node> n)
{
m_children.push_back(n);
n->parent = this;
n->m_manager = m_manager;
YGNodeInsertChild(y_node, n->y_node, YGNodeGetChildCount(y_node));
n->added(this);
}
void Node::add_child(std::shared_ptr<Node> n, int index)
{
m_children.push_back(n);
n->parent = this;
n->m_manager = m_manager;
YGNodeInsertChild(y_node, n->y_node, index);
n->added(this);
}
void Node::remove_child(Node* n)
{
auto i = std::find_if(m_children.begin(), m_children.end(), [=](auto& ptr) { return ptr.get() == n; });
if (i != m_children.end())
{
n->removed(this);
YGNodeRemoveChild(y_node, n->y_node);
m_children.erase(i);
}
}
void Node::remove_all_children()
{
for (auto& n : m_children)
{
n->removed(this);
YGNodeRemoveChild(y_node, n->y_node);
}
m_children.clear();
}
void Node::move_child(Node* n, int index)
{
YGNodeRemoveChild(y_node, n->y_node);
YGNodeInsertChild(y_node, n->y_node, index);
}
void Node::move_child_offset(Node* n, int offset)
{
int count = YGNodeGetChildCount(y_node);
for (int i = 0; i < count; i++)
{
if (YGNodeGetChild(y_node, i) == n->y_node)
{
int new_index = glm::clamp<int>(i + offset, 0, count - 1);
YGNodeRemoveChild(y_node, n->y_node);
YGNodeInsertChild(y_node, n->y_node, new_index);
break;
}
}
}
int Node::get_child_index(Node* n)
{
int count = YGNodeGetChildCount(y_node);
for (int i = 0; i < count; i++)
{
if (YGNodeGetChild(y_node, i) == n->y_node)
{
return i;
}
}
return -1;
}
Node* Node::get_child_at(int index)
{
auto n = YGNodeGetChild(y_node, index);
for (auto& c : m_children)
{
if (c->y_node == n)
return c.get();
}
return nullptr;
}
glm::vec4 Node::get_children_rect() const
{
if (m_children.empty())
return glm::vec4(0);
glm::vec4 ret = m_children[0]->m_clip_uncut;
for (auto& c : m_children)
ret = rect_union(ret, c->m_clip_uncut);
return ret;
}
void Node::mouse_capture()
{
// already owner of capture
if (root()->current_mouse_capture == this)
return;
// cancel previous owner
if (auto n = root()->current_mouse_capture)
{
MouseEvent e;
e.m_type = kEventType::MouseCancel;
n->handle_event(&e);
}
// make current
root()->current_mouse_capture = this;
m_mouse_captured = true;
}
void Node::mouse_release()
{
if (root()->current_mouse_capture == this)
root()->current_mouse_capture = nullptr;
m_mouse_captured = false;
}
void Node::key_capture()
{
root()->current_key_capture = this;
m_key_captured = true;
}
void Node::key_release()
{
root()->current_key_capture = nullptr;
m_key_captured = false;
}
Node&& Node::operator=(Node&& o)
{
return std::forward<Node>(o);
}
Node::Node()
{
y_node = YGNodeNew();
}
Node::Node(Node&& o)
{
m_name = std::move(o.m_name);
m_nodeID_s = std::move(o.m_nodeID_s);
m_children = std::move(o.m_children);
for (auto& c : m_children)
c->parent = this;
m_nodeID = o.m_nodeID;
m_display = o.m_display;
parent = o.parent;
y_node = o.y_node;
m_pos = o.m_pos;
m_size = o.m_size;
m_clip = o.m_clip;
m_zoom = o.m_zoom;
m_mouse_ignore = o.m_mouse_ignore;
o.y_node = nullptr;
o.parent = nullptr;
m_manager = o.m_manager;
current_mouse_capture = o.current_mouse_capture;
current_key_capture = o.current_key_capture;
m_mouse_captured = o.m_mouse_captured;
m_key_captured = o.m_key_captured;
m_proj = o.m_proj;
m_mvp = o.m_mvp;
m_mouse_inside = o.m_mouse_inside;
m_flood_events = o.m_flood_events;
m_capture_children = o.m_capture_children;
m_destroyed = o.m_destroyed;
m_scale = o.m_scale;
m_pos_origin = o.m_pos_origin;
m_pos_offset = o.m_pos_offset;
m_pos_offset_childred = o.m_pos_offset_childred;
m_clip_uncut = o.m_clip_uncut;
}
Node::~Node()
{
m_children.clear();
if (y_node)
YGNodeFree(y_node);
}
void Node::SetWidth(float value)
{
YGNodeStyleSetWidth(y_node, value);
m_size.x = value;
}
void Node::SetWidthP(float value)
{
YGNodeStyleSetWidthPercent(y_node, value);
}
void Node::SetHeight(float value)
{
YGNodeStyleSetHeight(y_node, value);
m_size.y = value;
}
void Node::SetHeightP(float value)
{
YGNodeStyleSetHeightPercent(y_node, value);
}
void Node::SetSize(float w, float h)
{
SetWidth(w); SetHeight(h);
m_size = {w, h};
}
void Node::SetSize(glm::vec2 value)
{
SetWidth(value.x); SetHeight(value.y);
m_size = value;
}
void Node::SetPadding(float t, float r, float b, float l)
{
YGNodeStyleSetPadding(y_node, YGEdgeTop, t);
YGNodeStyleSetPadding(y_node, YGEdgeRight, r);
YGNodeStyleSetPadding(y_node, YGEdgeBottom, b);
YGNodeStyleSetPadding(y_node, YGEdgeLeft, l);
}
glm::vec4 Node::GetPadding() const
{
float t = YGNodeLayoutGetPadding(y_node, YGEdgeTop);
float r = YGNodeLayoutGetPadding(y_node, YGEdgeRight);
float b = YGNodeLayoutGetPadding(y_node, YGEdgeBottom);
float l = YGNodeLayoutGetPadding(y_node, YGEdgeLeft);
return{ t, r, b, l };
}
void Node::SetPosition(const glm::vec2 pos)
{
SetPosition(pos.x, pos.y);
m_pos = pos;
}
void Node::SetPosition(float l, float t)
{
YGNodeStyleSetPosition(y_node, YGEdgeTop, t);
YGNodeStyleSetPosition(y_node, YGEdgeLeft, l);
m_pos = {l, t};
}
void Node::SetPosition(float l, float t, float r, float b)
{
YGNodeStyleSetPosition(y_node, YGEdgeTop, t);
YGNodeStyleSetPosition(y_node, YGEdgeRight, r);
YGNodeStyleSetPosition(y_node, YGEdgeBottom, b);
YGNodeStyleSetPosition(y_node, YGEdgeLeft, l);
}
void Node::SetFlexGrow(float value)
{
YGNodeStyleSetFlexGrow(y_node, value);
}
void Node::SetFlexShrink(float value)
{
YGNodeStyleSetFlexShrink(y_node, value);
}
void Node::SetFlexDir(YGFlexDirection value)
{
YGNodeStyleSetFlexDirection(y_node, value);
}
void Node::SetFlexWrap(YGWrap value)
{
YGNodeStyleSetFlexWrap(y_node, value);
}
void Node::SetJustify(YGJustify value)
{
YGNodeStyleSetJustifyContent(y_node, value);
}
void Node::SetAlign(YGAlign value)
{
YGNodeStyleSetAlignItems(y_node, value);
}
void Node::SetPositioning(YGPositionType value)
{
YGNodeStyleSetPositionType(y_node, value);
}
void Node::SetAspectRatio(float ar)
{
YGNodeStyleSetAspectRatio(y_node, ar);
}
void Node::SetRTL(YGDirection dir)
{
YGNodeStyleSetDirection(y_node, dir);
}
glm::vec2 Node::GetPosition()
{
return{ YGNodeLayoutGetLeft(y_node), YGNodeLayoutGetTop(y_node) };
}
float Node::GetWidth()
{
return YGNodeLayoutGetWidth(y_node);
}
float Node::GetHeight()
{
return YGNodeLayoutGetHeight(y_node);
}
glm::vec2 Node::GetSize()
{
return{ GetWidth(), GetHeight() };
}
YGDirection Node::GetRTL()
{
return YGNodeStyleGetDirection(y_node);
}
void Node::restore_context()
{
for (auto& c : m_children)
c->restore_context();
}
void Node::clear_context()
{
for (auto& c : m_children)
c->clear_context();
}
void Node::update(float width, float height, float zoom)
{
m_zoom = zoom;
YGNodeStyleSetWidth(y_node, width / zoom);
YGNodeStyleSetHeight(y_node, height / zoom);
YGNodeCalculateLayout(y_node, YGUndefined, YGUndefined, YGDirectionLTR);
m_proj = glm::ortho(0.f, width / zoom, height / zoom, 0.f, -1.f, 1.f);
update_internal({ 0, 0 }, m_proj);
}
void Node::update()
{
YGNodeCalculateLayout(y_node, YGUndefined, YGUndefined, YGDirectionLTR);
update_internal({ 0, 0 }, m_proj);
}
void Node::update_internal(const glm::vec2& origin, const glm::mat4& proj)
{
float x = YGNodeLayoutGetLeft(y_node);
float y = YGNodeLayoutGetTop(y_node);
float w = YGNodeLayoutGetWidth(y_node);
float h = YGNodeLayoutGetHeight(y_node);
auto old_size = m_size;
glm::vec2 parent_offset = parent ? parent->m_pos_offset_childred : glm::vec2(0.f);
m_pos = glm::floor(origin + glm::vec2(x, y) + m_pos_offset + parent_offset);
m_pos_origin = glm::floor(origin + glm::vec2(x, y));
m_size = glm::floor(glm::vec2(w, h));
if (parent)
{
// correct the padding clip
// should not clip the padded area
// useful to draw decorations
float pt = 0;//YGNodeLayoutGetPadding(parent->y_node, YGEdgeTop);
float pr = 0;//YGNodeLayoutGetPadding(parent->y_node, YGEdgeRight);
float pb = 0;//YGNodeLayoutGetPadding(parent->y_node, YGEdgeBottom);
float pl = 0;//YGNodeLayoutGetPadding(parent->y_node, YGEdgeLeft);
glm::vec2 off_p(pl, pt);
glm::vec2 off_s(pr, pb);
m_clip_uncut = glm::vec4(m_pos - off_p - glm::vec2(1), m_size + off_p + off_s + glm::vec2(2));
m_clip = rect_intersection(m_clip_uncut, parent->m_clip);
}
else
{
m_clip_uncut = m_clip = glm::vec4(m_pos, m_size);
}
glm::mat4 pivot = glm::translate(glm::vec3(.5f, .5f, 0.f));
glm::mat4 scale = glm::scale(glm::vec3(m_size, 1.f));
glm::mat4 prescale = glm::scale(glm::vec3(m_scale, 1.f));
glm::mat4 pos = glm::translate(glm::vec3(m_pos, 0));
m_mvp = proj * pos * scale * pivot * prescale;
m_proj = proj;
for (int i = 0; i < m_children.size(); i++)
{
if (m_children[i]->m_destroyed)
{
remove_child(m_children[i].get());
i--;
}
}
if (m_size != old_size)
handle_resize(old_size, m_size);
for (auto& c : m_children)
c->update_internal(m_pos, proj);
}
void Node::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
switch (ka)
{
case kAttribute::id:
m_nodeID_s = attr->Value();
m_nodeID = const_hash(attr->Value());
break;
case kAttribute::Width:
if (strcmp(attr->Value(), "auto") == 0)
{
YGNodeStyleSetWidth(y_node, YGUndefined);
YGNodeStyleSetWidthPercent(y_node, YGUndefined);
}
else
{
if (strchr(attr->Value(), '%'))
YGNodeStyleSetWidthPercent(y_node, attr->FloatValue());
else
YGNodeStyleSetWidth(y_node, attr->FloatValue());
}
break;
case kAttribute::MinWidth:
YGNodeStyleSetMinWidth(y_node, attr->FloatValue());
break;
case kAttribute::MaxWidth:
YGNodeStyleSetMaxWidth(y_node, attr->FloatValue());
break;
case kAttribute::Height:
if (strcmp(attr->Value(), "auto") == 0)
{
YGNodeStyleSetHeight(y_node, YGUndefined);
YGNodeStyleSetHeightPercent(y_node, YGUndefined);
}
else
{
if (strchr(attr->Value(), '%'))
YGNodeStyleSetHeightPercent(y_node, attr->FloatValue());
else
YGNodeStyleSetHeight(y_node, attr->FloatValue());
}
break;
case kAttribute::MinHeight:
YGNodeStyleSetMinHeight(y_node, attr->FloatValue());
break;
case kAttribute::MaxHeight:
YGNodeStyleSetMaxHeight(y_node, attr->FloatValue());
break;
case kAttribute::Grow:
YGNodeStyleSetFlexGrow(y_node, attr->FloatValue());
break;
case kAttribute::Shrink:
YGNodeStyleSetFlexShrink(y_node, attr->FloatValue());
break;
case kAttribute::FlexDir:
{
YGFlexDirection dir = YGFlexDirectionRow;
if (strcmp("col", attr->Value()) == 0)
dir = YGFlexDirectionColumn;
else if (strcmp("col-reverse", attr->Value()) == 0)
dir = YGFlexDirectionColumnReverse;
else if (strcmp("row", attr->Value()) == 0)
dir = YGFlexDirectionRow;
else if (strcmp("row-reverse", attr->Value()) == 0)
dir = YGFlexDirectionRowReverse;
YGNodeStyleSetFlexDirection(y_node, dir);
break;
}
case kAttribute::FlexWrap:
YGNodeStyleSetFlexWrap(y_node, attr->IntValue() ? YGWrapWrap : YGWrapNoWrap);
break;
case kAttribute::Justify:
{
YGJustify v = YGJustifyFlexStart;
if (strcmp("center", attr->Value()) == 0)
v = YGJustifyCenter;
else if (strcmp("flex-start", attr->Value()) == 0)
v = YGJustifyFlexStart;
else if (strcmp("flex-end", attr->Value()) == 0)
v = YGJustifyFlexEnd;
else if (strcmp("space-around", attr->Value()) == 0)
v = YGJustifySpaceAround;
else if (strcmp("space-between", attr->Value()) == 0)
v = YGJustifySpaceBetween;
YGNodeStyleSetJustifyContent(y_node, v);
break;
}
case kAttribute::Align:
{
YGAlign v = YGAlignStretch;
if (strcmp("stretch", attr->Value()) == 0)
v = YGAlignStretch;
else if (strcmp("flex-start", attr->Value()) == 0)
v = YGAlignFlexStart;
else if (strcmp("flex-end", attr->Value()) == 0)
v = YGAlignFlexEnd;
else if (strcmp("center", attr->Value()) == 0)
v = YGAlignCenter;
YGNodeStyleSetAlignItems(y_node, v);
break;
}
case kAttribute::Positioning:
{
YGPositionType v = YGPositionTypeRelative;
if (strcmp("relative", attr->Value()) == 0)
v = YGPositionTypeRelative;
else if (strcmp("absolute", attr->Value()) == 0)
v = YGPositionTypeAbsolute;
YGNodeStyleSetPositionType(y_node, v);
break;
}
case kAttribute::Position:
{
glm::vec4 v;
int n = sscanf(attr->Value(), "%f %f %f %f", &v.x, &v.y, &v.z, &v.w);
if (n == 2)
{
YGNodeStyleSetPosition(y_node, YGEdgeLeft, v.x);
YGNodeStyleSetPosition(y_node, YGEdgeTop, v.y);
}
else
{
YGNodeStyleSetPadding(y_node, YGEdgeLeft, v.x);
YGNodeStyleSetPadding(y_node, YGEdgeTop, v.y);
YGNodeStyleSetPadding(y_node, YGEdgeRight, v.z);
YGNodeStyleSetPadding(y_node, YGEdgeBottom, v.w);
}
break;
}
case kAttribute::Padding:
{
glm::vec4 pad;
int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
if (n == 1)
{
YGNodeStyleSetPadding(y_node, YGEdgeTop, pad.x);
YGNodeStyleSetPadding(y_node, YGEdgeRight, pad.x);
YGNodeStyleSetPadding(y_node, YGEdgeBottom, pad.x);
YGNodeStyleSetPadding(y_node, YGEdgeLeft, pad.x);
}
else
{
YGNodeStyleSetPadding(y_node, YGEdgeTop, pad.x);
YGNodeStyleSetPadding(y_node, YGEdgeRight, pad.y);
YGNodeStyleSetPadding(y_node, YGEdgeBottom, pad.z);
YGNodeStyleSetPadding(y_node, YGEdgeLeft, pad.w);
}
break;
}
case kAttribute::Margin:
{
glm::vec4 pad;
int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
if (n == 1)
{
YGNodeStyleSetMargin(y_node, YGEdgeTop, pad.x);
YGNodeStyleSetMargin(y_node, YGEdgeRight, pad.x);
YGNodeStyleSetMargin(y_node, YGEdgeBottom, pad.x);
YGNodeStyleSetMargin(y_node, YGEdgeLeft, pad.x);
}
else
{
YGNodeStyleSetMargin(y_node, YGEdgeTop, pad.x);
YGNodeStyleSetMargin(y_node, YGEdgeRight, pad.y);
YGNodeStyleSetMargin(y_node, YGEdgeBottom, pad.z);
YGNodeStyleSetMargin(y_node, YGEdgeLeft, pad.w);
}
break;
}
case kAttribute::FloodEvents:
m_flood_events = attr->IntValue() > 0;
break;
case kAttribute::AspectRatio:
YGNodeStyleSetAspectRatio(y_node, attr->FloatValue());
break;
case kAttribute::RTL:
if (strcmp("rtl", attr->Value()) == 0)
SetRTL(YGDirectionRTL);
else if (strcmp("ltr", attr->Value()) == 0)
SetRTL(YGDirectionLTR);
else if (strcmp("inherit", attr->Value()) == 0)
SetRTL(YGDirectionInherit);
else
{
LOG("Attribute %s for RTL unrecognized", attr->Value());
}
default:
break;
}
}
void Node::load_internal(const tinyxml2::XMLElement* x_node)
{
m_name = x_node->Name();
//LOG("node %s", m_name.c_str());
init();
auto attr = x_node->FirstAttribute();
while (attr)
{
parse_attributes((kAttribute)const_hash(attr->Name()), attr);
attr = attr->Next();
}
create();
auto x_child = x_node->FirstChildElement();
while (x_child)
{
kWidget child_id = (kWidget)const_hash(x_child->Name());
switch (child_id)
{
#define CASE(W,C) case W: { auto n = new C(); add_child(n); n->load_internal(x_child); break; }
CASE(kWidget::Node, Node);
CASE(kWidget::Border, NodeBorder);
CASE(kWidget::Image, NodeImage);
CASE(kWidget::ImageTexture, NodeImageTexture);
CASE(kWidget::Icon, NodeIcon);
CASE(kWidget::Text, NodeText);
CASE(kWidget::TextInput, NodeTextInput);
CASE(kWidget::Button, NodeButton);
CASE(kWidget::ButtonCustom, NodeButtonCustom);
CASE(kWidget::ComboBox, NodeComboBox);
CASE(kWidget::SliderH, NodeSliderH);
CASE(kWidget::SliderV, NodeSliderV);
CASE(kWidget::SliderHue, NodeSliderHue);
CASE(kWidget::PopupMenu, NodePopupMenu);
CASE(kWidget::Viewport, NodeViewport);
CASE(kWidget::CheckBox, NodeCheckBox);
CASE(kWidget::Layer, NodeLayer);
CASE(kWidget::PanelLayer, NodePanelLayer);
CASE(kWidget::PanelBrush, NodePanelBrush);
CASE(kWidget::PanelColor, NodePanelColor);
CASE(kWidget::PanelStroke, NodePanelStroke);
CASE(kWidget::PanelGrid, NodePanelGrid);
CASE(kWidget::ColorQuad, NodeColorQuad);
CASE(kWidget::StrokePreview, NodeStrokePreview);
CASE(kWidget::Canvas, NodeCanvas);
CASE(kWidget::Scroll, NodeScroll);
CASE(kWidget::DialogBrowse, NodeDialogBrowse);
CASE(kWidget::DialogBrowseItem, NodeDialogBrowseItem);
CASE(kWidget::DialogCloud, NodeDialogCloud);
CASE(kWidget::DialogCloudItem, NodeDialogCloudItem);
CASE(kWidget::ColorWheel, NodeColorWheel);
CASE(kWidget::ColorPicker, NodeColorPicker);
#undef CASE
case kWidget::Ref:
{
auto ids = x_child->Attribute("id");
auto id = const_hash(ids);
auto& ref = (*m_manager)[id]->m_children[0];
auto n = ref->clone();
n->m_nodeID_s = ids;
n->m_nodeID = id;
add_child(n);
break;
}
default:
{
LOG("instancing UNKNOWN node: %s", x_child->Name());
auto n = new Node();
add_child(n);
n->load_internal(x_child);
break;
}
}
x_child = x_child->NextSiblingElement();
}
loaded();
}
void Node::draw()
{
}
Node* Node::clone()
{
Node* n = clone_instantiate();
clone_copy(n);
clone_children(n);
clone_finalize(n);
return n;
}
Node* Node::clone_instantiate() const
{
return new Node();
}
void Node::clone_copy(Node* dest) const
{
YGNodeCopyStyle(dest->y_node, y_node);
dest->m_manager = m_manager;
dest->m_name = m_name;
dest->m_nodeID_s = m_nodeID_s;
dest->m_nodeID = m_nodeID;
dest->m_display = m_display;
dest->m_pos = m_pos;
dest->m_size = m_size;
dest->m_clip = m_clip;
dest->m_flood_events = m_flood_events;
dest->m_manager = m_manager;
dest->current_mouse_capture = current_mouse_capture;
dest->current_key_capture = current_key_capture;
dest->m_mouse_captured = m_mouse_captured;
dest->m_key_captured = m_key_captured;
dest->m_proj = m_proj;
dest->m_mvp = m_mvp;
dest->m_mouse_inside = m_mouse_inside;
dest->m_capture_children = m_capture_children;
dest->m_destroyed = m_destroyed;
dest->m_scale = m_scale;
dest->m_pos_origin = m_pos_origin;
dest->m_pos_offset = m_pos_offset;
dest->m_pos_offset_childred = m_pos_offset_childred;
dest->m_clip_uncut = m_clip_uncut;
}
void Node::clone_children(Node* dest) const
{
for (auto& c : m_children)
{
Node* cn = c->clone();
dest->m_children.emplace_back(cn);
cn->parent = dest;
cn->m_manager = dest->m_manager;
cn->loaded();
YGNodeInsertChild(dest->y_node, cn->y_node, YGNodeGetChildCount(dest->y_node));
}
}
void Node::clone_finalize(Node* dest) const
{
/* find controllers and stuff */
}

224
src/node.h Normal file
View File

@@ -0,0 +1,224 @@
#pragma once
#include "event.h"
enum class kAttribute : uint16_t
{
id = const_hash("id"),
Width = const_hash("width"),
MinWidth = const_hash("min-width"),
MaxWidth = const_hash("max-width"),
Height = const_hash("height"),
MinHeight = const_hash("min-height"),
MaxHeight = const_hash("max-height"),
Divisions = const_hash("divisions"),
InnerRadius = const_hash("inner-radius"),
OuterRadius = const_hash("outer-radius"),
Grow = const_hash("grow"),
Shrink = const_hash("shrink"),
FlexDir = const_hash("dir"),
FlexWrap = const_hash("wrap"),
Padding = const_hash("pad"),
Margin = const_hash("margin"),
Color = const_hash("color"),
Thickness = const_hash("thickness"),
BorderColor = const_hash("border-color"),
Type = const_hash("type"),
Text = const_hash("text"),
FontFace = const_hash("font-face"),
FontSize = const_hash("font-size"),
Justify = const_hash("justify"),
Align = const_hash("align"),
Path = const_hash("path"),
Region = const_hash("region"),
Position = const_hash("position"),
Positioning = const_hash("positioning"),
FloodEvents = const_hash("flood-events"),
Icon = const_hash("icon"),
Selected = const_hash("selected"),
Template = const_hash("template"),
Value = const_hash("value"),
Range = const_hash("range"),
AspectRatio = const_hash("aspect-ratio"),
ComboList = const_hash("combo-list"),
Mips = const_hash("mips"),
Default = const_hash("default"),
RTL = const_hash("rtl"),
};
enum class kWidget : uint16_t
{
Node = const_hash("node"),
Border = const_hash("border"),
Shape = const_hash("shape"),
Text = const_hash("text"),
TextInput = const_hash("text-input"),
Image = const_hash("image"),
ImageTexture = const_hash("image-texture"),
Icon = const_hash("icon"),
Button = const_hash("button"),
ButtonCustom = const_hash("button-custom"),
ComboBox = const_hash("combobox"),
SliderH = const_hash("slider-h"),
SliderV = const_hash("slider-v"),
SliderHue = const_hash("slider-hue"),
PopupMenu = const_hash("popup-menu"),
Viewport = const_hash("viewport"),
Ref = const_hash("ref"),
CheckBox = const_hash("checkbox"),
Layer = const_hash("layer"),
PanelLayer = const_hash("panel-layer"),
PanelBrush = const_hash("panel-brush"),
PanelColor = const_hash("panel-color"),
PanelStroke = const_hash("panel-stroke"),
PanelGrid = const_hash("panel-grid"),
ColorQuad = const_hash("color-quad"),
StrokePreview = const_hash("stroke-preview"),
Canvas = const_hash("canvas"),
Scroll = const_hash("scroll"),
DialogBrowse = const_hash("dialog-browse"),
DialogBrowseItem = const_hash("dialog-browse-item"),
DialogCloud = const_hash("dialog-cloud"),
DialogCloudItem = const_hash("dialog-cloud-item"),
ColorWheel = const_hash("colorwheel"),
ColorPicker = const_hash("color-picker"),
};
class Node
{
friend class LayoutManager;
public:
Node* parent{ nullptr };
YGNodeRef y_node{ nullptr };
class LayoutManager* m_manager;
uint16_t m_nodeID;
std::string m_nodeID_s;
std::vector<std::shared_ptr<Node>> m_children;
Node* current_mouse_capture = nullptr;
Node* current_key_capture = nullptr;
bool m_mouse_captured = false;
bool m_key_captured = false;
glm::mat4 m_proj;
glm::mat4 m_mvp;
bool m_mouse_inside = false;
bool m_flood_events = false;
bool m_capture_children = true; // wether to capture children events when xx_capture() is used
bool m_destroyed = false;
bool m_mouse_ignore = true;
float m_zoom = 1.f;
glm::vec2 m_scale{ 1, 1 };
glm::vec2 m_pos{ 0, 0 };
glm::vec2 m_pos_origin{ 0, 0 }; // original layout position without offset
glm::vec2 m_pos_offset{ 0, 0 }; // artificial position offset for scrolling
glm::vec2 m_pos_offset_childred{ 0, 0 }; // artificial position offset for scrolling
glm::vec2 m_size{ 0, 0 };
glm::vec4 m_clip{ 0, 0, 0, 0 };
glm::vec4 m_clip_uncut{ 0, 0, 0, 0 };
std::string m_name;
bool m_display = true;
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
Node&& operator=(Node&& o);
Node(Node&& o);
Node();
~Node();
void SetWidth(float value);
void SetWidthP(float value);
void SetHeight(float value);
void SetHeightP(float value);
void SetSize(glm::vec2 value);
void SetSize(float w, float h);
void SetPadding(float t, float r, float b, float l);
glm::vec4 GetPadding() const;
void SetPosition(float l, float t, float r, float b);
void SetPosition(float l, float t);
void SetPosition(const glm::vec2 pos);
void SetFlexGrow(float value);
void SetFlexShrink(float value);
void SetFlexDir(YGFlexDirection value);
void SetFlexWrap(YGWrap value);
void SetJustify(YGJustify value);
void SetAlign(YGAlign value);
void SetPositioning(YGPositionType value);
void SetAspectRatio(float ar);
void SetRTL(YGDirection dir);
glm::vec2 GetPosition();
float GetWidth();
float GetHeight();
glm::vec2 GetSize();
YGDirection GetRTL();
virtual void restore_context();;
virtual void clear_context();
void update(float width, float height, float zoom);
void update();
void update_internal(const glm::vec2& origin, const glm::mat4& proj);
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr);
void load_internal(const tinyxml2::XMLElement* x_node);
virtual void draw();
Node* clone();
virtual Node* clone_instantiate() const;
virtual void clone_copy(Node* dest) const;
virtual void clone_children(Node* dest) const;
virtual void clone_finalize(Node* dest) const;;
void watch(std::function<bool(Node*)> observer);
void destroy();
Node* root();
template<class T = Node> T* find(const char* ids)
{
uint16_t id = const_hash(ids);
if (id == m_nodeID)
return static_cast<T*>(this);
for (auto& c : m_children)
if (auto found = c->find(ids))
return static_cast<T*>(found);
return nullptr;
}
template<class T> T* add_child()
{
auto* n = new T;
n->m_manager = m_manager;
n->parent = parent;
n->init();
n->create();
n->loaded();
add_child(n);
return n;
}
virtual kEventResult on_event(Event* e);
virtual kEventResult handle_event(Event* e);
virtual void handle_resize(glm::vec2 old_size, glm::vec2 new_size);;
virtual void create();
virtual void init();
virtual void loaded();
virtual void added(Node* parent);
virtual void removed(Node* parent);
const Node* init_template(const char* id);
void async_start();
void async_update();
void async_end();
void add_child(Node* n);
void add_child(Node* n, int index);
void add_child(std::shared_ptr<Node> n);
void add_child(std::shared_ptr<Node> n, int index);
void remove_child(Node* n);
void remove_all_children();
void move_child(Node* n, int index);
void move_child_offset(Node* n, int offset);
int get_child_index(Node* n);
Node* get_child_at(int index);
glm::vec4 get_children_rect() const;
void mouse_capture();
void mouse_release();
void key_capture();
void key_release();
};

83
src/node_border.cpp Normal file
View File

@@ -0,0 +1,83 @@
#include "pch.h"
#include "log.h"
#include "node_border.h"
#include "shader.h"
ui::Plane NodeBorder::m_plane;
NodeBorder::NodeBorder()
{
m_mouse_ignore = false;
}
void NodeBorder::static_init()
{
m_plane.create<1>(1, 1);
}
Node* NodeBorder::clone_instantiate() const
{
return new NodeBorder();
}
void NodeBorder::clone_copy(Node* dest) const
{
Node::clone_copy(dest);
NodeBorder* n = static_cast<NodeBorder*>(dest);
n->m_color = m_color;
n->m_border_color = m_border_color;
n->m_thinkness = m_thinkness;
n->m_mouse_ignore = false;
}
void NodeBorder::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
Node::parse_attributes(ka, attr);
switch (ka)
{
case kAttribute::Color:
{
glm::vec4 pad;
int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
if (n == 1)
m_color = glm::vec4(pad.x, pad.x, pad.x, 1);
else
m_color = pad;
break;
}
case kAttribute::BorderColor:
{
glm::vec4 pad;
int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
if (n == 1)
m_border_color = glm::vec4(pad.x);
else
m_border_color = pad;
break;
}
case kAttribute::Thickness:
m_thinkness = attr->FloatValue();
break;
default:
break;
}
}
void NodeBorder::draw()
{
using namespace ui;
ui::ShaderManager::use(kShader::Color);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
if (m_color.a > 0.f)
{
m_color.a < 1.f ? glEnable(GL_BLEND) : glDisable(GL_BLEND);
ui::ShaderManager::u_vec4(kShaderUniform::Col, m_color);
m_plane.draw_fill();
glDisable(GL_BLEND);
}
if (m_thinkness > 0 && m_border_color.a > 0.f)
{
glLineWidth(m_thinkness);
ui::ShaderManager::u_vec4(kShaderUniform::Col, m_border_color);
m_border_color.a < 1.f ? glEnable(GL_BLEND) : glDisable(GL_BLEND);
m_plane.draw_stroke();
glDisable(GL_BLEND);
}
}

18
src/node_border.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include "node.h"
#include "shape.h"
class NodeBorder : public Node
{
public:
static ui::Plane m_plane;
glm::vec4 m_color{ 0, 0, 0, 1 };
glm::vec4 m_border_color{ 1, 1, 1, 1 };
float m_thinkness{ 0 };
NodeBorder();
static void static_init();
virtual Node* clone_instantiate() const override;
virtual void clone_copy(Node* dest) const override;
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override;
virtual void draw() override;
};

127
src/node_button.cpp Normal file
View File

@@ -0,0 +1,127 @@
#include "pch.h"
#include "log.h"
#include "node_button.h"
Node* NodeButton::clone_instantiate() const
{
return new NodeButton();
}
void NodeButton::clone_children(Node* dest) const
{
Node::clone_children(dest);
NodeButton* n = static_cast<NodeButton*>(dest);
n->m_border = (NodeBorder*)n->m_children[0].get();
n->m_text = (NodeText*)n->m_border->m_children[0].get();
}
void NodeButton::clone_copy(Node* dest) const
{
Node::clone_copy(dest);
NodeButton* n = static_cast<NodeButton*>(dest);
//n->m_border = (NodeBorder*)m_border->clone();
//n->m_text = (NodeText*)m_text->clone();
n->color_normal = color_normal;
n->color_hover = color_hover;
n->color_down = color_down;
//n->on_click = on_click;
n->m_mouse_ignore = false;
}
void NodeButton::init()
{
m_border = new NodeBorder();
m_text = new NodeText();
add_child(m_border);
m_border->add_child(m_text);
m_border->init();
m_border->m_color = color_normal;
m_text->init();
m_text->m_font = "arial";
m_text->m_font_size = 11;
m_border->SetAlign(YGAlignCenter);
m_border->SetJustify(YGJustifyCenter);
m_border->m_mouse_ignore = false;
m_mouse_ignore = false;
}
void NodeButton::create()
{
m_border->create();
m_text->create();
m_border->m_mouse_ignore = false;
m_mouse_ignore = false;
}
void NodeButton::loaded()
{
m_border->m_thinkness = 1;
m_border->m_border_color = glm::vec4(0, 0, 0, 1);
m_border->m_color = color_normal;
m_border->m_mouse_ignore = false;
m_mouse_ignore = false;
}
void NodeButton::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
switch (ka)
{
case kAttribute::Color:
m_border->parse_attributes(ka, attr);
color_normal = m_border->m_color;
color_hover = glm::clamp(m_border->m_color + glm::vec4(.1, .1, .1, 0), {0,0,0,0}, {1,1,1,1});
color_down = glm::clamp(m_border->m_color + glm::vec4(.2, .2, .2, 0), {0,0,0,0}, {1,1,1,1});
case kAttribute::Padding:
case kAttribute::Width:
case kAttribute::Height:
case kAttribute::Thickness:
case kAttribute::BorderColor:
m_border->parse_attributes(ka, attr);
break;
case kAttribute::Text:
case kAttribute::FontFace:
case kAttribute::FontSize:
m_text->parse_attributes(ka, attr);
break;
default:
Node::parse_attributes(ka, attr);
break;
}
// m_border->parse_attributes(ka, attr);
// m_text->parse_attributes(ka, attr);
}
void NodeButton::set_color(const glm::vec4& c)
{
color_normal = c;
m_border->m_color = color_normal;
}
kEventResult NodeButton::handle_event(Event* e)
{
Node::handle_event(e);
switch (e->m_type)
{
case kEventType::MouseEnter:
m_border->m_color = color_hover;
break;
case kEventType::MouseLeave:
m_border->m_color = color_normal;
break;
case kEventType::MouseDownL:
m_border->m_color = color_down;
break;
case kEventType::MouseUpL:
m_border->m_color = color_normal;
if (m_mouse_inside && on_click != nullptr)
on_click(this);
break;
case kEventType::MouseCancel:
m_border->m_color = color_normal;
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}

24
src/node_button.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include "node.h"
#include "node_border.h"
#include "node_text.h"
class NodeButton : public Node
{
public:
NodeBorder* m_border;
NodeText* m_text;
glm::vec4 color_normal{ .1, .1, .1, 1 };
glm::vec4 color_hover{ .2, .2, .2, 1 };
glm::vec4 color_down{ .3, .3, .3, 1 };
std::function<void(Node* target)> on_click;
virtual Node* clone_instantiate() const override;
virtual void clone_children(Node* dest) const override;
virtual void clone_copy(Node* dest) const override;
virtual void init() override;
virtual void create() override;
virtual void loaded() override;
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override;
void set_color(const glm::vec4& c);
virtual kEventResult handle_event(Event* e) override;
};

View File

@@ -0,0 +1,83 @@
#include "pch.h"
#include "log.h"
#include "node_button_custom.h"
Node* NodeButtonCustom::clone_instantiate() const
{
return new NodeButtonCustom();
}
void NodeButtonCustom::clone_copy(Node* dest) const
{
NodeBorder::clone_copy(dest);
NodeButtonCustom* n = static_cast<NodeButtonCustom*>(dest);
n->color_normal = color_normal;
n->color_hover = color_hover;
n->color_down = color_down;
n->m_mouse_ignore = false;
n->m_color = color_normal;
}
void NodeButtonCustom::loaded()
{
NodeBorder::loaded();
//m_thinkness = 1;
//m_border_color = glm::vec4(0, 0, 0, 1);
m_color = color_normal;
m_mouse_ignore = false;
}
void NodeButtonCustom::set_color(const glm::vec4& c)
{
color_normal = c;
m_color = color_normal;
}
kEventResult NodeButtonCustom::handle_event(Event* e)
{
NodeBorder::handle_event(e);
switch (e->m_type)
{
case kEventType::MouseEnter:
m_color = color_hover;
break;
case kEventType::MouseLeave:
m_color = color_normal;
break;
case kEventType::MouseDownL:
m_color = color_down;
mouse_capture();
break;
case kEventType::MouseUpL:
#ifdef __IOS__
m_color = color_normal;
#else
m_color = m_mouse_inside ? color_hover : color_normal;
#endif
mouse_release();
if (m_mouse_inside && on_click != nullptr)
on_click(this);
break;
case kEventType::MouseCancel:
m_color = color_normal;
mouse_release();
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}
void NodeButtonCustom::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
NodeBorder::parse_attributes(ka, attr);
switch (ka)
{
case kAttribute::Color:
color_normal = m_color;
break;
default:
break;
}
}

17
src/node_button_custom.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include "node_border.h"
class NodeButtonCustom : public NodeBorder
{
public:
glm::vec4 color_normal{ .2, .2, .2, 1 };
glm::vec4 color_hover{ .3, .3, .3, 1 };
glm::vec4 color_down{ .4, .4, .4, 1 };
std::function<void(Node* target)> on_click;
virtual Node* clone_instantiate() const override;
virtual void clone_copy(Node* dest) const override;
virtual void loaded() override;
void set_color(const glm::vec4& c);
virtual kEventResult handle_event(Event* e) override;
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override;
};

451
src/node_canvas.cpp Normal file
View File

@@ -0,0 +1,451 @@
#include "pch.h"
#include "app.h"
#include "log.h"
#include "node_canvas.h"
#include "node_image_texture.h"
Node* NodeCanvas::clone_instantiate() const
{
return new NodeCanvas();
}
void NodeCanvas::init()
{
m_mouse_ignore = false;
m_canvas = std::make_unique<ui::Canvas>();
m_canvas->create(CANVAS_RES, CANVAS_RES);
m_canvas->m_unsaved = false;
m_canvas->m_node = this;
m_sampler.create();
m_sampler.set_filter(GL_LINEAR, GL_NEAREST);
m_sampler_linear.create(GL_LINEAR);
m_sampler_stencil.create(GL_LINEAR, GL_REPEAT);
m_face_plane.create<1>(2, 2);
m_line.create();
CanvasMode::node = this;
CanvasMode::canvas = m_canvas.get();
for (int i = 0; i < (int)ui::Canvas::kCanvasMode::COUNT; i++)
for (auto m : ui::Canvas::modes[i])
m->init();
m_grid.create(1, 1, m_grid_divs);
}
void NodeCanvas::restore_context()
{
Node::restore_context();
m_canvas->create(CANVAS_RES, CANVAS_RES);
m_sampler.create();
m_sampler.set_filter(GL_LINEAR, GL_NEAREST);
m_face_plane.create<1>(2, 2);
m_canvas->snapshot_restore();
CanvasMode::node = this;
CanvasMode::canvas = m_canvas.get();
for (int i = 0; i < (int)ui::Canvas::kCanvasMode::COUNT; i++)
for (auto m : ui::Canvas::modes[i])
m->init();
}
void NodeCanvas::clear_context()
{
Node::clear_context();
m_canvas->snapshot_save(data_path);
m_canvas->clear_context();
// TODO: clear CanvasMode objects
}
void NodeCanvas::draw()
{
using namespace ui;
GLint vp[4];
GLfloat cc[4];
glGetIntegerv(GL_VIEWPORT, vp);
glGetFloatv(GL_COLOR_CLEAR_VALUE, cc);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
float zoom = root()->m_zoom;
auto box = m_clip * zoom;
glm::ivec4 c = (glm::ivec4)glm::vec4(box.x, (int)(vp[3] - box.y - box.w), box.z, box.w);
glViewport(c.x, c.y, c.z, c.w);
//m_canvas->m_cam_rot = m_pan * 0.003f;
glm::mat4 ortho_proj = glm::ortho(0.f, box.z, 0.f, box.w, -1000.f, 1000.f);
glm::mat4 proj = glm::perspective(glm::radians(m_canvas->m_cam_fov), box.z / box.w, 0.01f, 1000.f);
glm::mat4 camera = glm::eulerAngleXY(m_canvas->m_cam_rot.y, m_canvas->m_cam_rot.x) *
glm::translate(m_canvas->m_cam_pos);
m_canvas->m_mv = camera;
m_canvas->m_proj = proj;
m_canvas->m_box = box;
m_canvas->m_vp = c;
// auto plane_mvp = proj * camera * transform *
// glm::scale(glm::vec3(sz, 1));
m_sampler.bind(0);
m_sampler.bind(1);
m_sampler.bind(2);
m_sampler_stencil.bind(3);
auto blend = glIsEnabled(GL_BLEND);
auto depth = glIsEnabled(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);
for (int plane_index = 0; plane_index < 6; plane_index++)
{
auto plane_mvp = proj * camera *
glm::scale(glm::vec3(m_canvas->m_order.size() + 500)) *
m_canvas->m_plane_transform[plane_index] *
glm::translate(glm::vec3(0, 0, -1));
ui::ShaderManager::use(kShader::Checkerboard);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp);
m_face_plane.draw_fill();
}
glEnable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);
float pitch = 0;
if (auto slider = root()->find<NodeSliderH>("pitch-slider"))
pitch = (slider->get_value() - 0.5) * glm::half_pi<float>();
float yaw = 0;
if (auto slider = root()->find<NodeSliderH>("yaw-slider"))
yaw = (slider->get_value() - 0.5) * glm::half_pi<float>();
float roll = 0;
if (auto slider = root()->find<NodeSliderH>("roll-slider"))
roll = (slider->get_value() - 0.5) * glm::half_pi<float>();
for (size_t i = 0; i < m_canvas->m_order.size(); i++)
{
auto layer_index = m_canvas->m_order[i];
for (int plane_index = 0; plane_index < 6; plane_index++)
{
if (m_canvas->m_layers[layer_index].m_opacity == .0f)
continue;
int z = (int)m_canvas->m_order.size() - i;
auto plane_mvp_z = proj * camera *
glm::scale(glm::vec3(z + 1)) *
glm::eulerAngleYXZ(yaw, pitch, roll) *
m_canvas->m_plane_transform[plane_index] *
glm::translate(glm::vec3(0, 0, -1));
if (m_canvas->m_state == ui::Canvas::kCanvasMode::Erase && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index)
{
m_sampler.bind(0);
ui::ShaderManager::use(kShader::CompErase);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_int(kShaderUniform::TexStroke, 1);
//ui::ShaderManager::u_int(kShaderUniform::TexMask, 2);
ui::ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_current_stroke->m_brush.m_tip_opacity);
//ui::ShaderManager::u_int(kShaderUniform::Lock, m_canvas->m_layers[layer_index].m_alpha_locked);
//ui::ShaderManager::u_int(kShaderUniform::Mask, m_canvas->m_smask_active);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
glActiveTexture(GL_TEXTURE0);
m_canvas->m_layers[layer_index].m_rtt[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE1);
m_canvas->m_tmp[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE2);
m_canvas->m_smask.m_rtt[plane_index].bindTexture();
m_face_plane.draw_fill();
m_canvas->m_smask.m_rtt[plane_index].unbindTexture();
glActiveTexture(GL_TEXTURE1);
m_canvas->m_tmp[plane_index].unbindTexture();
glActiveTexture(GL_TEXTURE0);
m_canvas->m_layers[layer_index].m_rtt[plane_index].unbindTexture();
}
else if(m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index)
{
m_sampler.bind(0);
auto& paper = TextureManager::get(const_hash("data/paper.jpg"));
ui::ShaderManager::use(kShader::CompDraw);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_int(kShaderUniform::TexStroke, 1);
ui::ShaderManager::u_int(kShaderUniform::TexMask, 2);
//ui::ShaderManager::u_int(kShaderUniform::TexStencil, 3);
ui::ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_current_stroke->m_brush.m_tip_opacity);
ui::ShaderManager::u_int(kShaderUniform::Lock, m_canvas->m_layers[layer_index].m_alpha_locked);
ui::ShaderManager::u_int(kShaderUniform::Mask, m_canvas->m_smask_active);
ui::ShaderManager::u_int(kShaderUniform::BlendMode, m_canvas->m_current_stroke->m_brush.m_blend_mode);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
glActiveTexture(GL_TEXTURE0);
m_canvas->m_layers[layer_index].m_rtt[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE1);
m_canvas->m_tmp[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE2);
m_canvas->m_smask.m_rtt[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE3);
paper.bind();
m_face_plane.draw_fill();
paper.unbind();
glActiveTexture(GL_TEXTURE2);
m_canvas->m_smask.m_rtt[plane_index].unbindTexture();
glActiveTexture(GL_TEXTURE1);
m_canvas->m_tmp[plane_index].unbindTexture();
glActiveTexture(GL_TEXTURE0);
m_canvas->m_layers[layer_index].m_rtt[plane_index].unbindTexture();
}
else
{
m_sampler.bind(0);
m_sampler_linear.bind(1);
ui::ShaderManager::use(kShader::TextureAlphaSep);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_int(kShaderUniform::TexA, 1);
ui::ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_layers[layer_index].m_opacity);
ui::ShaderManager::u_int(kShaderUniform::Highlight, m_canvas->m_layers[layer_index].m_hightlight);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
glActiveTexture(GL_TEXTURE0);
m_canvas->m_layers[layer_index].m_rtt[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE1);
m_canvas->m_layers[layer_index].m_rtt[plane_index].bindTexture();
m_face_plane.draw_fill();
glActiveTexture(GL_TEXTURE1);
m_canvas->m_layers[layer_index].m_rtt[plane_index].unbindTexture();
glActiveTexture(GL_TEXTURE0);
m_canvas->m_layers[layer_index].m_rtt[plane_index].unbindTexture();
}
// if (m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index)
// {
// glDisable(GL_BLEND);
// ui::ShaderManager::use(kShader::TextureAlpha);
// ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
// ui::ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_layers[layer_index].m_opacity);
// ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
// ui::ShaderManager::u_float(kShaderUniform::Alpha, 1);
// m_canvas->m_tmp[plane_index].bindTexture();
// m_face_plane.draw_fill();
// m_canvas->m_tmp[plane_index].unbindTexture();
// glEnable(GL_BLEND);
// }
}
}
/*
if (m_canvas->m_smask_active && !m_canvas->m_show_tmp)
{
glDisable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);
for (int plane_index = 0; plane_index < 6; plane_index++)
{
auto plane_mvp = proj * camera *
m_canvas->m_plane_transform[plane_index] *
glm::translate(glm::vec3(0, 0, -1));
ui::ShaderManager::use(kShader::TextureAlpha);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_float(kShaderUniform::Alpha, 0.5f);
ui::ShaderManager::u_int(kShaderUniform::Highlight, 0);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp);
glActiveTexture(GL_TEXTURE0);
m_canvas->m_smask.m_rtt[plane_index].bindTexture();
m_face_plane.draw_fill();
m_canvas->m_smask.m_rtt[plane_index].unbindTexture();
}
}
*/
for (auto& mode : *m_canvas->m_mode)
mode->on_Draw(ortho_proj, proj, camera);
glDisable(GL_DEPTH_TEST);
if (m_canvas->m_smask_active)
{
m_canvas->modes[(int)Canvas::kCanvasMode::MaskFree][0]->on_Draw(ortho_proj, proj, camera);
m_canvas->modes[(int)Canvas::kCanvasMode::MaskLine][0]->on_Draw(ortho_proj, proj, camera);
}
// keep drawing the grids
if (m_canvas->m_state != ui::Canvas::kCanvasMode::Grid)
for (auto& mode : ui::Canvas::modes[(int)ui::Canvas::kCanvasMode::Grid])
mode->on_Draw(ortho_proj, proj, camera);
if (App::I.grid->m_groud_opacity->get_value() > 0.f)
{
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);
// DRAW GRIDS
ui::ShaderManager::use(kShader::Color);
// ground grid
int grid_divs = glm::floor(App::I.grid->m_groud_scale->get_value() * 100);
if (grid_divs != m_grid_divs && (grid_divs % 2) == 0)
{
m_grid_divs = grid_divs;
m_grid.create(1, 1, grid_divs);
}
float grid_scale = m_grid_divs * 0.01f;
ui::ShaderManager::u_vec4(kShaderUniform::Col, glm::vec4(
glm::vec3(App::I.grid->m_groud_value->get_value()),
App::I.grid->m_groud_opacity->get_value()));
ui::ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera
* glm::translate(glm::vec3(0, glm::pow(App::I.grid->m_groud_height->get_value() - 0.5f, 3), 0))
* glm::eulerAngleX(glm::radians(90.f))
* glm::scale(glm::vec3(grid_scale, grid_scale, 1))
);
//m_grid.draw_stroke();
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
App::I.grid->m_hm_plane.draw_fill();
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
App::I.grid->m_hm_plane.draw_stroke();
}
// box grid
// ceiling
// ui::ShaderManager::u_vec4(kShaderUniform::Col, glm::vec4(
// glm::vec3(App::I.grid->m_groud_value->get_value()),
// App::I.grid->m_box_opacity->get_value()));
// ui::ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera
// * glm::translate(glm::vec3(0, (App::I.grid->m_groud_height->get_value() + App::I.grid->m_box_height->get_value() - 0.5f) * 2.f, 0))
// * glm::eulerAngleX(glm::radians(90.f))
// * glm::scale(glm::vec3(grid_scale, grid_scale, 1))
// );
// m_grid.draw_stroke();
//ui::ShaderManager::use(kShader::Equirect);
//ui::ShaderManager::u_mat4(kShaderUniform::MVP, glm::scale(glm::vec3(.5, .5, 1)));
//ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
//glBindTexture(GL_TEXTURE_CUBE_MAP, m_canvas->cube_id);
//m_face_plane.draw_fill();
//glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
// ui::ShaderManager::use(kShader::Color);
// ui::ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera);
// ui::ShaderManager::u_vec4(kShaderUniform::Col, { 1, 0, 0, 1 });
// static glm::vec4 AB[4]{ {-.75, 0, -1, 1},{ -.75, 0, 1, 1 } };
// m_line.update_vertices(AB);
// m_line.draw_stroke();
blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND);
depth ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST);
m_sampler.unbind();
glViewport(vp[0], vp[1], vp[2], vp[3]);
glClearColor(cc[0], cc[1], cc[2], cc[3]);
}
void NodeCanvas::handle_resize(glm::vec2 old_size, glm::vec2 new_size)
{
if (new_size.x != m_canvas->m_width || new_size.y != m_canvas->m_height)
{
#if __IOS__
m_canvas->m_mixer.create((int)new_size.x * m_canvas->m_mixer_scale,
(int)new_size.y * m_canvas->m_mixer_scale, -1, GL_RGBA16F);
#else
m_canvas->m_mixer.create((int)new_size.x * m_canvas->m_mixer_scale,
(int)new_size.y * m_canvas->m_mixer_scale, -1, GL_RGBA32F);
#endif
if (auto img = root()->find<NodeImageTexture>("tex-debug"))
img->tex.assign(m_canvas->m_mixer.getTextureID());
// m_canvas->resize((int)new_size.x, (int)new_size.y);
// m_canvas->clear();
}
}
kEventResult NodeCanvas::handle_event(Event* e)
{
static std::vector<CanvasMode>* old_mode = nullptr;
Node::handle_event(e);
MouseEvent* me = static_cast<MouseEvent*>(e);
KeyEvent* ke = static_cast<KeyEvent*>(e);
GestureEvent* ge = static_cast<GestureEvent*>(e);
auto loc = (me->m_pos - m_pos) * root()->m_zoom;
switch (e->m_type)
{
case kEventType::MouseScroll:
case kEventType::MouseDownL:
case kEventType::MouseUpL:
case kEventType::MouseDownR:
case kEventType::MouseUpR:
case kEventType::MouseMove:
case kEventType::MouseCancel:
m_canvas->m_cur_pos = loc;
if (!(m_canvas->m_touch_lock && me->m_source == kEventSource::Touch))
for (auto& mode : *m_canvas->m_mode)
mode->on_MouseEvent(me, loc);
break;
case kEventType::KeyDown:
if (ke->m_key == kKey::KeyE)
ui::Canvas::set_mode(ui::Canvas::kCanvasMode::Erase);
if (ke->m_key == kKey::AndroidBack)
if (!ActionManager::empty())
ActionManager::undo();
break;
case kEventType::KeyUp:
if (ke->m_key == kKey::KeyE)
ui::Canvas::set_mode(ui::Canvas::kCanvasMode::Draw);
if (ke->m_key == kKey::KeyTab)
App::I.toggle_ui();
if (ke->m_key == kKey::KeyZ && App::I.keys[(int)kKey::KeyCtrl])
App::I.keys[(int)kKey::KeyShift] ? ActionManager::redo() : ActionManager::undo();
if (ke->m_key == kKey::KeyS && App::I.keys[(int)kKey::KeyCtrl] && !App::I.keys[(int)kKey::KeyShift])
{
if (ui::Canvas::I->m_newdoc)
{
App::I.dialog_save();
}
else if (ui::Canvas::I->m_unsaved)
{
ui::Canvas::I->project_save();
}
}
if (ke->m_key == kKey::KeyS && App::I.keys[(int)kKey::KeyCtrl] && App::I.keys[(int)kKey::KeyShift])
{
if (ui::Canvas::I->m_newdoc)
{
App::I.dialog_save();
}
else if (ui::Canvas::I->m_unsaved)
{
App::I.dialog_save_ver();
}
}
break;
case kEventType::GestureStart:
mouse_capture();
for (auto& mode : *m_canvas->m_mode)
mode->on_GestureEvent(ge);
break;
case kEventType::GestureMove:
for (auto& mode : *m_canvas->m_mode)
mode->on_GestureEvent(ge);
break;
case kEventType::GestureEnd:
mouse_release();
for (auto& mode : *m_canvas->m_mode)
mode->on_GestureEvent(ge);
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}
void NodeCanvas::reset_camera()
{
m_canvas->m_cam_rot = {0, 0};
m_canvas->m_cam_pos = {0, 0, 0};
m_canvas->m_cam_fov = 85;
m_canvas->m_pan = {0, 0};
}

25
src/node_canvas.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include "node.h"
#include "canvas.h"
class NodeCanvas : public Node
{
public:
std::string data_path;
std::unique_ptr<ui::Canvas> m_canvas;
Sampler m_sampler;
Sampler m_sampler_linear;
Sampler m_sampler_stencil;
ui::Plane m_face_plane;
ui::LineSegment m_line;
ui::Plane m_grid;
int m_grid_divs = 30;
virtual Node* clone_instantiate() const override;
virtual void init() override;
virtual void restore_context() override;
virtual void clear_context() override;
virtual void draw() override;
virtual void handle_resize(glm::vec2 old_size, glm::vec2 new_size) override;
virtual kEventResult handle_event(Event* e) override;
void reset_camera();
};

75
src/node_checkbox.cpp Normal file
View File

@@ -0,0 +1,75 @@
#include "pch.h"
#include "log.h"
#include "node_checkbox.h"
Node* NodeCheckBox::clone_instantiate() const
{
return new NodeCheckBox();
}
void NodeCheckBox::clone_children(Node* dest) const
{
Node::clone_children(dest);
NodeCheckBox* n = static_cast<NodeCheckBox*>(dest);
n->m_outer = (NodeBorder*)n->m_children[0].get();
n->m_inner = (NodeBorder*)n->m_outer->m_children[0].get();
n->m_mouse_ignore = false;
}
void NodeCheckBox::init()
{
m_outer = new NodeBorder();
m_inner = new NodeBorder();
add_child(m_outer);
m_outer->add_child(m_inner);
m_outer->init();
m_outer->m_color = { .3, .3, .3, 1 };
m_outer->SetAlign(YGAlignCenter);
m_outer->SetJustify(YGJustifyCenter);
m_outer->SetPadding(5, 5, 5, 5);
m_outer->SetWidthP(100);
m_outer->SetHeightP(100);
m_outer->m_mouse_ignore = false;
m_inner->init();
m_inner->SetWidthP(100);
m_inner->SetHeightP(100);
m_inner->m_border_color = glm::vec4(.8, .8, .8, 1);
m_inner->m_thinkness = 1;
m_inner->m_color = glm::vec4(.8, .8, .8, 1);
m_mouse_ignore = false;
}
void NodeCheckBox::create()
{
m_outer->create();
m_inner->create();
}
kEventResult NodeCheckBox::handle_event(Event* e)
{
Node::handle_event(e);
switch (e->m_type)
{
case kEventType::MouseEnter:
break;
case kEventType::MouseLeave:
break;
case kEventType::MouseDownL:
break;
case kEventType::MouseUpL:
checked = !checked;
if (on_value_changed)
on_value_changed(this, checked);
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}
void NodeCheckBox::draw()
{
m_inner->m_color = checked ? glm::vec4(.4, .4, .4, 1) : glm::vec4(.8, .8, .8, 1);
Node::draw();
}

18
src/node_checkbox.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include "node.h"
#include "node_border.h"
class NodeCheckBox : public Node
{
public:
std::function<void(Node* target, bool checked)> on_value_changed;
NodeBorder* m_outer;
NodeBorder* m_inner;
bool checked = false;
virtual Node* clone_instantiate() const override;
virtual void clone_children(Node* dest) const override;
virtual void init() override;
virtual void create() override;
virtual kEventResult handle_event(Event* e) override;
virtual void draw() override;
};

99
src/node_color_quad.cpp Normal file
View File

@@ -0,0 +1,99 @@
#include "pch.h"
#include "log.h"
#include "node_color_quad.h"
#include "shader.h"
Node* NodeColorQuad::clone_instantiate() const
{
return new NodeColorQuad();
}
void NodeColorQuad::clone_finalize(Node* dest) const
{
auto n = (NodeColorQuad*)dest;
n->m_picker = (NodeBorder*)n->m_children[0].get();
}
void NodeColorQuad::init()
{
m_picker = new NodeBorder;
m_picker->SetSize({ 20, 20 });
m_picker->SetPositioning(YGPositionTypeAbsolute);
m_picker->SetPosition(0, 0);
m_picker->m_thinkness = 1;
m_picker->m_color = glm::vec4(0);
add_child(m_picker);
}
void NodeColorQuad::set_value(float x, float y)
{
auto sz = m_size;
auto pos = glm::clamp(glm::vec2(x, y) * sz, { 0, 0 }, sz);
//m_picker->SetPosition(pos - m_picker->GetSize() * .5f);
m_value = pos / glm::max({ 1,1 }, sz); // avoid div0
if (on_value_changed)
on_value_changed(this, m_value);
}
kEventResult NodeColorQuad::handle_event(Event* e)
{
NodeBorder::handle_event(e);
switch (e->m_type)
{
case kEventType::MouseDownL:
{
m_old_value = m_value;
dragging = true;
mouse_capture();
auto sz = m_size;
auto pos = glm::clamp(((MouseEvent*)e)->m_pos - m_pos, { 0, 0 }, sz);
m_picker->SetPosition(pos - m_picker->GetSize() * .5f);
m_value = pos / glm::max({ 1,1 }, sz); // avoid div0
if (on_value_changed)
on_value_changed(this, m_value);
}
break;
case kEventType::MouseUpL:
mouse_release();
dragging = false;
break;
case kEventType::MouseMove:
if (dragging)
{
auto sz = m_size;
auto pos = glm::clamp(((MouseEvent*)e)->m_pos - m_pos, { 0, 0 }, sz);
m_picker->SetPosition(pos - m_picker->GetSize() * .5f);
m_value = pos / glm::max({ 1,1 }, sz); // avoid div0
if (on_value_changed)
on_value_changed(this, m_value);
}
break;
case kEventType::MouseCancel:
mouse_release();
dragging = false;
m_value = m_old_value;
set_value(m_value.x, m_value.y);
if (on_value_changed)
on_value_changed(this, m_value);
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}
void NodeColorQuad::draw()
{
m_picker->m_border_color = m_value.y > .5f ? glm::vec4(1) : glm::vec4(0, 0, 0, 1);
auto sz = m_size;
auto pos = glm::clamp(m_value * sz, { 0, 0 }, sz);
m_picker->SetPosition(pos - m_picker->GetSize() * .5f);
using namespace ui;
ui::ShaderManager::use(kShader::ColorQuad);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
ui::ShaderManager::u_vec4(kShaderUniform::Col, glm::vec4(convert_rgb2hsv(glm::vec3(m_color)), 1));
m_plane.draw_fill();
}

18
src/node_color_quad.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include "node_border.h"
class NodeColorQuad : public NodeBorder
{
NodeBorder* m_picker{nullptr};
bool dragging = false;
public:
glm::vec2 m_value{0.f};
glm::vec2 m_old_value{0.f};
std::function<void(Node* target, glm::vec2 value)> on_value_changed;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void set_value(float x, float y);
virtual kEventResult handle_event(Event* e) override;
virtual void draw() override;
};

189
src/node_colorwheel.cpp Normal file
View File

@@ -0,0 +1,189 @@
#include "pch.h"
#include "node_colorwheel.h"
#include "shader.h"
#include "log.h"
Node* NodeColorWheel::clone_instantiate() const
{
return new NodeColorWheel;
}
void NodeColorWheel::clone_finalize(Node* dest) const
{
NodeColorWheel* n = (NodeColorWheel*)dest;
n->init_controls();
n->m_mouse_ignore = false;
}
void NodeColorWheel::init()
{
m_mouse_ignore = false;
//init_template("color-picker");
init_controls();
}
void NodeColorWheel::init_controls()
{
}
void NodeColorWheel::loaded()
{
m_circle.create<64>(.5, .4, ui::Circle::kUVMapping::Tube);
m_cur_hue.create<16>(.05, 0.04);
m_cur_quad.create<16>(.04, 0.03, ui::Circle::kUVMapping::Tube);
float quad_scale = glm::sin(glm::radians(45.f)) * 0.8f;
m_quad.create<1>(quad_scale, quad_scale);
struct vertex_t { glm::vec4 pos; glm::vec2 uvs; glm::vec4 col; };
std::vector<vertex_t> vertices;
float l = 0.4;
vertices.push_back({{glm::cos(4.f/3.f*glm::pi<float>())*l,glm::sin(4.f/3.f*glm::pi<float>())*l,0,1},{1,-1},{1,1,1,1}});
vertices.push_back({{glm::cos(2.f/3.f*glm::pi<float>())*l,glm::sin(2.f/3.f*glm::pi<float>())*l,0,1},{0,0},{0,0,0,1}});
vertices.push_back({{l,0,0,1},{1,1},{1,0,0,1}});
glGenBuffers(1, &buffers);
glBindBuffer(GL_ARRAY_BUFFER, buffers);
glBufferData(GL_ARRAY_BUFFER, vertices.size()*sizeof(vertex_t), vertices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glGenVertexArrays(1, &arrays);
glBindVertexArray(arrays);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, buffers);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, uvs));
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, col));
glBindVertexArray(0);
}
void NodeColorWheel::draw()
{
using namespace ui;
glDisable(GL_BLEND);
ShaderManager::use(kShader::ColorHue);
ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp * glm::eulerAngleZ(glm::radians(-90.f)));
ShaderManager::u_int(kShaderUniform::Direction, 0); // set horizontal
m_circle.draw_fill();
// ShaderManager::use(kShader::ColorTri);
// ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
// ShaderManager::u_vec4(kShaderUniform::Col, glm::vec4(m_hsv, 0.f));
// GLenum type = GL_TRIANGLES;
// glBindVertexArray(arrays);
// glDrawArrays(type, 0, 3);
// glBindVertexArray(0);
ShaderManager::use(kShader::Color);
ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp * glm::eulerAngleZ(glm::radians(-360.f * m_hsv.x)) * glm::translate(glm::vec3(.45f,0.f,0.f)));
ShaderManager::u_vec4(kShaderUniform::Col, {convert_hsv2rgb({glm::fract(m_hsv.x + 0.5f), 1.f, 1.f}), 1.f});
m_cur_hue.draw_stroke();
ShaderManager::use(kShader::ColorQuad);
ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
ShaderManager::u_vec4(kShaderUniform::Col, glm::vec4(m_hsv, 0.f));
m_quad.draw_fill();
float quad_scale = glm::sin(glm::radians(45.f)) * 0.8f;
glm::vec3 pos = glm::vec3(glm::vec2(m_hsv.y, 1.f - m_hsv.z) - 0.5f, 0);
ShaderManager::use(kShader::Color);
ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp * glm::translate(pos * quad_scale));
ShaderManager::u_vec4(kShaderUniform::Col, {convert_hsv2rgb({glm::fract(m_hsv.x + 0.5f), 1.f, 1.f}), 1.f});
m_cur_quad.draw_fill();
}
glm::vec4 NodeColorWheel::get_quad_rect() const
{
float quad_scale = glm::sin(glm::radians(45.f)) * 0.8f;
glm::vec2 size = m_size * quad_scale;
glm::vec2 pos = (m_size - size) * 0.5f;
return glm::vec4(pos, size);
}
bool NodeColorWheel::inside_quad(glm::vec2 pos, glm::vec2& out_coord) const
{
auto r = get_quad_rect();
out_coord = (pos - xy(r)) / zw(r);
return point_in_rect(pos, r);
}
void NodeColorWheel::handle_color_change()
{
if (on_value_changed)
on_value_changed(this, m_hsv);
}
kEventResult NodeColorWheel::handle_event(Event* e)
{
Node::handle_event(e);
auto* me = static_cast<MouseEvent*>(e);
switch (e->m_type)
{
case kEventType::MouseDownL:
{
m_old_value = m_hsv;
dragging = true;
mouse_capture();
auto pos = (me->m_pos - m_pos - GetSize() * 0.5f) / GetSize();
float l = glm::length(pos);
glm::vec2 quad_pos(0);
if (inside_quad(me->m_pos - m_pos, quad_pos))
{
mode = 2;
m_hsv.y = glm::clamp(quad_pos.x, 0.f, 1.f);
m_hsv.z = 1.f - glm::clamp(quad_pos.y, 0.f, 1.f);
handle_color_change();
}
else if (l >= 0.4f && l <= 0.5f)
{
mode = 1;
auto pos = glm::normalize(me->m_pos - m_pos - GetSize() * 0.5f);
m_hsv.x = (glm::atan(pos.y, -pos.x) + glm::pi<float>()) / glm::two_pi<float>();
handle_color_change();
}
else
{
mode = 0;
}
}
break;
case kEventType::MouseUpL:
mouse_release();
dragging = false;
break;
case kEventType::MouseMove:
if (dragging)
{
if (mode == 1)
{
auto pos = glm::normalize(me->m_pos - m_pos - GetSize() * 0.5f);
m_hsv.x = (glm::atan(pos.y, -pos.x) + glm::pi<float>()) / glm::two_pi<float>();
handle_color_change();
}
else if (mode == 2)
{
glm::vec2 quad_pos(0);
inside_quad(me->m_pos - m_pos, quad_pos);
m_hsv.y = glm::clamp(quad_pos.x, 0.f, 1.f);
m_hsv.z = 1.f - glm::clamp(quad_pos.y, 0.f, 1.f);
handle_color_change();
}
}
break;
case kEventType::MouseCancel:
mouse_release();
dragging = false;
m_hsv = m_old_value;
handle_color_change();
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}

35
src/node_colorwheel.h Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#include "util.h"
#include "node.h"
#include "shape.h"
#include "node_slider.h"
#include "node_border.h"
class NodeColorWheel : public Node
{
public:
ui::Circle m_circle;
ui::Plane m_cur_hue;
ui::Circle m_cur_quad;
ui::Plane m_quad;
NodeBorder* m_color_cur;
glm::vec3 m_hsv;
GLuint m_tri_vbo;
GLuint m_tri_vao;
GLuint buffers;
GLuint arrays;
std::function<void(Node* target, glm::vec3 hsv)> on_value_changed;
int mode; // 1:hue 2:quad
bool dragging = false;
glm::vec3 m_old_value{0.f};
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
virtual void loaded() override;
virtual void draw() override;
virtual kEventResult handle_event(Event* e) override;
void init_controls();
glm::vec4 get_quad_rect() const;
bool inside_quad(glm::vec2 pos, glm::vec2& out_coord) const;
void handle_color_change();
};

92
src/node_combobox.cpp Normal file
View File

@@ -0,0 +1,92 @@
#include "pch.h"
#include "log.h"
#include "node_combobox.h"
#include "node_popup_menu.h"
Node* NodeComboBox::clone_instantiate() const
{
return new NodeComboBox;
}
void NodeComboBox::clone_copy(Node* dest) const
{
NodeButton::clone_copy(dest);
NodeComboBox* n = static_cast<NodeComboBox*>(dest);
n->m_data = m_data;
n->m_current_index = m_current_index;
}
void NodeComboBox::loaded()
{
NodeButton::loaded();
on_click = [this](Node* target) {
NodePopupMenu* popup = new NodePopupMenu;
popup->init();
popup->create();
popup->loaded();
root()->add_child(popup);
m_items.clear();
for (int i = 0; i < m_data.size(); i++)
{
if (m_data[i] == "-")
{
auto n = popup->add_child<NodeBorder>();
n->SetHeight(5.f);
n->SetWidthP(100.f);
n->m_color = {0, 0, 0, 1};
}
else
{
auto btn = popup->add_child<NodeButton>();
btn->m_text->set_text(m_data[i].c_str());
btn->m_border->SetWidthP(100.f);
btn->m_border->SetHeight(30.f);
int index = (int)m_items.size();
m_items.push_back(m_data[i]);
btn->on_click = [this,popup,btn,index](Node* target) {
m_current_index = index;
m_selected_child_index = popup->get_child_index(target);
m_text->set_text(m_items[index].c_str());
popup->mouse_release();
popup->destroy();
if (on_select)
on_select(btn, index);
};
}
}
float offset = 0;
for (int i = 0; i <= m_selected_child_index; i++)
offset += (m_data[i] == "-") ? 5.f : 30.f;
float height = m_items.size() * 30.f + (m_data.size() - m_items.size()) * 5.f; // add items and separators
glm::vec2 pos = m_pos + glm::vec2(0, m_size.y - offset);
auto screen = root()->m_size;
if ((pos.y + height) > screen.y) pos.y = screen.y - height;
if (pos.y < 0) pos.y = 0;
popup->SetPositioning(YGPositionTypeAbsolute);
popup->SetPosition(pos.x, pos.y);
popup->SetSize(m_size.x, YGUndefined);
popup->SetFlexGrow(1.f);
popup->update();
root()->update();
popup->mouse_capture();
popup->m_mouse_ignore = false;
popup->m_flood_events = true;
popup->m_capture_children = false;
};
}
void NodeComboBox::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
NodeButton::parse_attributes(ka, attr);
switch (ka)
{
case kAttribute::ComboList:
{
m_data = split(attr->Value(), ',');
break;
}
case kAttribute::Default:
m_current_index = attr->IntValue();
break;
}
}

16
src/node_combobox.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "node_button.h"
class NodeComboBox : public NodeButton
{
public:
std::function<void(Node* target, int index)> on_select;
std::vector<std::string> m_data;
std::vector<std::string> m_items;
int m_current_index = 0;
int m_selected_child_index = 0;
virtual Node* clone_instantiate() const override;
virtual void clone_copy(Node* dest) const override;
virtual void loaded() override;
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override;
};

166
src/node_dialog_browse.cpp Normal file
View File

@@ -0,0 +1,166 @@
#include "pch.h"
#include "log.h"
#include "node_dialog_browse.h"
#include "canvas.h"
#include "node_image_texture.h"
#include "asset.h"
#include "node_message_box.h"
#include "app.h"
Node* NodeDialogBrowse::clone_instantiate() const
{
return new NodeDialogBrowse();
}
void NodeDialogBrowse::clone_finalize(Node* dest) const
{
NodeDialogBrowse* n = static_cast<NodeDialogBrowse*>(dest);
n->init_controls();
}
void NodeDialogBrowse::init()
{
auto tpl = static_cast<const NodeBorder*>(init_template("dialog-browse"));
m_color = tpl->m_color;
m_border_color = tpl->m_border_color;;
m_thinkness = tpl->m_thinkness;;
init_controls();
}
void NodeDialogBrowse::init_controls()
{
btn_ok = find<NodeButton>("btn-ok");
btn_cancel = find<NodeButton>("btn-cancel");
btn_cancel->on_click = [this](Node*) {
destroy();
};
btn_delete = find<NodeButton>("btn-delete");
btn_delete->on_click = [this](Node*) {
if (!current)
return;
auto msgbox = new NodeMessageBox();
msgbox->m_manager = m_manager;
msgbox->init();
msgbox->m_title->set_text("Delete Project");
msgbox->m_message->set_text(("Are you sure you want to delete " + current->m_file_name + "?").c_str());
msgbox->btn_ok->on_click = [this,msgbox](Node*){
auto path = current->m_path;
int idx = container->get_child_index(current);
container->remove_child(current);
if (!container->m_children.empty())
{
int newidx = std::min<int>(idx, (int)container->m_children.size() - 1);
auto next = (NodeDialogBrowseItem*)container->get_child_at(newidx);
current = nullptr;
next->on_selected(next);
next->m_selected = true;
}
else
{
current = nullptr;
auto image_tex = find<NodeImageTexture>("thumb-tex");
image_tex->tex.destroy();
}
Asset::delete_file(path);
msgbox->destroy();
};
root()->add_child(msgbox);
root()->update();
};
container = find<Node>("files-list");
auto names = Asset::list_files(data_path, false, ".*\\.pano");
for (const auto& n : names)
{
auto node = new NodeDialogBrowseItem;
node->m_manager = m_manager;
node->init();
node->m_text->set_text(n.c_str());
node->m_path = data_path + "/" + n;
node->m_file_name = n;
node->on_selected = [&](NodeDialogBrowseItem* target) {
if (target == current)
return;
selected_path = target->m_path;
selected_file = target->m_file_name;
selected_name = selected_file.substr(0, selected_file.length() - 5);
if (current)
current->m_selected = false;
current = target;
};
// load thumb
ui::Image thumb = ui::Canvas::I->thumbnail_read(node->m_path);
auto image_tex = node->find<NodeImageTexture>("thumb-tex");
image_tex->tex.destroy();
image_tex->tex.create(thumb);
container->add_child(node);
}
// if (auto* first = (NodeDialogBrowseItem*)container->get_child_at(0))
// {
// first->on_selected(first);
// first->m_selected = true;
// }
}
void NodeDialogBrowse::loaded()
{
}
//////////////////////////////////////////////////////////////////
Node* NodeDialogBrowseItem::clone_instantiate() const
{
return new NodeDialogBrowseItem;
}
void NodeDialogBrowseItem::clone_finalize(Node* dest) const
{
NodeDialogBrowseItem* n = static_cast<NodeDialogBrowseItem*>(dest);
n->init_controls();
}
void NodeDialogBrowseItem::init()
{
auto tpl = static_cast<const NodeBorder*>(init_template("dialog-browse-item"));
m_color = tpl->m_color;
m_border_color = tpl->m_border_color;
m_thinkness = tpl->m_thinkness;
init_controls();
}
void NodeDialogBrowseItem::init_controls()
{
m_text = find<NodeText>("title");
}
void NodeDialogBrowseItem::loaded()
{
}
void NodeDialogBrowseItem::draw()
{
auto c = m_selected ? m_color_selected : m_color_normal;
m_thinkness = m_selected ? 1.f : 0.f;
m_color = m_mouse_inside ? m_color_hover : c;
NodeBorder::draw();
}
kEventResult NodeDialogBrowseItem::handle_event(Event* e)
{
NodeBorder::handle_event(e);
switch (e->m_type)
{
case kEventType::MouseEnter:
break;
case kEventType::MouseLeave:
break;
case kEventType::MouseDownL:
m_selected = true;
if (on_selected)
on_selected(this);
break;
case kEventType::MouseUpL:
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}

46
src/node_dialog_browse.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include "node_border.h"
#include "node_button.h"
#include "node_image_texture.h"
#include "node_text.h"
#include "node_text_input.h"
class NodeDialogBrowseItem : public NodeBorder
{
public:
NodeText* m_text;
NodeImageTexture* m_thumb;
glm::vec4 m_color_normal = glm::vec4(.4, .4, .4, 1);
glm::vec4 m_color_selected = glm::vec4(.3, .3, .3, 1);
glm::vec4 m_color_hover = glm::vec4(.5, .5, .5, 1);
bool m_selected = false;
std::string m_path;
std::string m_file_name;
std::function<void(NodeDialogBrowseItem* target)> on_selected;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
virtual void loaded() override;
virtual void draw() override;
virtual kEventResult handle_event(Event* e) override;
};
class NodeDialogBrowse : public NodeBorder
{
public:
NodeButton* btn_cancel;
NodeButton* btn_ok;
NodeButton* btn_delete;
NodeDialogBrowseItem* current = nullptr;
Node* container;
std::string selected_path;
std::string selected_file;
std::string selected_name;
std::string data_path;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
virtual void loaded() override;
};

211
src/node_dialog_cloud.cpp Normal file
View File

@@ -0,0 +1,211 @@
#include "pch.h"
#include "log.h"
#include "node_dialog_cloud.h"
#include "canvas.h"
#include "node_image_texture.h"
#include "asset.h"
#include "node_message_box.h"
#include "app.h"
#include "image.h"
Node* NodeDialogCloud::clone_instantiate() const
{
return new NodeDialogCloud();
}
void NodeDialogCloud::clone_finalize(Node* dest) const
{
NodeDialogCloud* n = static_cast<NodeDialogCloud*>(dest);
n->init_controls();
}
void NodeDialogCloud::init()
{
auto tpl = static_cast<const NodeBorder*>(init_template("dialog-cloud"));
m_color = tpl->m_color;
m_border_color = tpl->m_border_color;;
m_thinkness = tpl->m_thinkness;;
init_controls();
}
void NodeDialogCloud::init_controls()
{
btn_ok = find<NodeButton>("btn-ok");
btn_cancel = find<NodeButton>("btn-cancel");
btn_cancel->on_click = [this](Node*) {
destroy();
};
container = find<Node>("files-list");
std::thread(&NodeDialogCloud::load_thumbs_thread, this).detach();
}
void NodeDialogCloud::loaded()
{
}
void NodeDialogCloud::removed(Node* parent)
{
closed = true;
}
void NodeDialogCloud::load_thumbs_thread()
{
CURL *curl = curl_easy_init();
std::string res;
if (curl)
{
async_start();
auto* align = container->add_child<Node>();
align->SetWidthP(100.f);
align->SetHeightP(100.f);
align->SetAlign(YGAlignCenter);
align->SetJustify(YGJustifyCenter);
auto* text = align->add_child<NodeText>();
text->set_font(kFont::Arial_30);
text->set_text("Connecting to the server...");
async_update();
async_end();
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &res);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler);
curl_easy_setopt(curl, CURLOPT_URL, "http://omigamedev.com/panopainter/cloud/cloud-list.php");
auto err = curl_easy_perform(curl);
if (err != CURLE_OK)
{
async_start();
text->set_text("Could not connect to the server");
async_update();
async_end();
return;
}
async_start();
align->destroy();
async_end();
LOG("CLOUD LIST: %s", res.c_str());
auto names = split(res, ',');
std::vector<NodeDialogCloudItem*> nodes;
// create slots with name
App::I.async_start();
for (const auto& n : names)
{
auto node = new NodeDialogCloudItem;
node->m_manager = m_manager;
node->init();
node->m_text->set_text(n.c_str());
node->m_path = data_path + "/" + n;
node->m_file_name = n;
container->add_child(node);
node->on_selected = [&](NodeDialogCloudItem* target) {
if (target == current)
return;
selected_path = target->m_path;
selected_file = target->m_file_name;
selected_name = selected_file.substr(0, selected_file.length() - 5);
if (current)
current->m_selected = false;
current = target;
};
nodes.push_back(node);
}
App::I.async_update();
App::I.async_end();
// load the icons
for (int i = 0; i < names.size(); i++)
{
const auto& n = names[i];
auto* node = nodes[i];
if (closed)
break;
res.clear();
std::string url = "http://omigamedev.com/panopainter/cloud/cloud-info.php?file=" + n;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
LOG("%s", url.c_str());
auto err = curl_easy_perform(curl);
if (err != CURLE_OK)
break; // TODO: handle this error with a message or something
auto info = split(res, ',', 3);
int width = atoi(info[0].c_str());
int height = atoi(info[1].c_str());
int comp = atoi(info[2].c_str());
assert(comp == 4);
std::string rgb;
rgb.resize(Base64::DecodedLength(info[3]));
Base64::Decode(info[3], &rgb);
ui::Image thumb;
thumb.create(width, height);
thumb.copy_from((uint8_t*)rgb.data());
App::I.async_start();
auto image_tex = node->find<NodeImageTexture>("thumb-tex");
image_tex->tex.destroy();
image_tex->tex.create(thumb);
App::I.async_update();
App::I.async_end();
}
curl_easy_cleanup(curl);
}
}
//////////////////////////////////////////////////////////////////
Node* NodeDialogCloudItem::clone_instantiate() const
{
return new NodeDialogCloudItem;
}
void NodeDialogCloudItem::clone_finalize(Node* dest) const
{
NodeDialogCloudItem* n = static_cast<NodeDialogCloudItem*>(dest);
n->init_controls();
}
void NodeDialogCloudItem::init()
{
auto tpl = static_cast<const NodeBorder*>(init_template("dialog-cloud-item"));
m_color = tpl->m_color;
m_border_color = tpl->m_border_color;
m_thinkness = tpl->m_thinkness;
init_controls();
}
void NodeDialogCloudItem::init_controls()
{
m_text = find<NodeText>("title");
}
void NodeDialogCloudItem::loaded()
{
}
void NodeDialogCloudItem::draw()
{
auto c = m_selected ? m_color_selected : m_color_normal;
m_thinkness = m_selected ? 1.f : 0.f;
m_color = m_mouse_inside ? m_color_hover : c;
NodeBorder::draw();
}
kEventResult NodeDialogCloudItem::handle_event(Event* e)
{
NodeBorder::handle_event(e);
switch (e->m_type)
{
case kEventType::MouseEnter:
break;
case kEventType::MouseLeave:
break;
case kEventType::MouseDownL:
m_selected = true;
if (on_selected)
on_selected(this);
break;
case kEventType::MouseUpL:
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}

49
src/node_dialog_cloud.h Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include "node_border.h"
#include "node_button.h"
#include "node_image_texture.h"
#include "node_text.h"
#include "node_text_input.h"
class NodeDialogCloudItem : public NodeBorder
{
public:
NodeText* m_text;
NodeImageTexture* m_thumb;
glm::vec4 m_color_normal = glm::vec4(.4, .4, .4, 1);
glm::vec4 m_color_selected = glm::vec4(.3, .3, .3, 1);
glm::vec4 m_color_hover = glm::vec4(.5, .5, .5, 1);
bool m_selected = false;
std::string m_path;
std::string m_file_name;
std::function<void(NodeDialogCloudItem* target)> on_selected;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
virtual void loaded() override;
virtual void draw() override;
virtual kEventResult handle_event(Event* e) override;
};
class NodeDialogCloud : public NodeBorder
{
public:
bool closed = false;
NodeButton* btn_cancel;
NodeButton* btn_ok;
NodeButton* btn_delete;
NodeDialogCloudItem* current = nullptr;
Node* container;
std::string selected_path;
std::string selected_file;
std::string selected_name;
std::string data_path;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
virtual void loaded() override;
virtual void removed(Node* parent) override;
void load_thumbs_thread();
};

View File

@@ -0,0 +1,47 @@
#include "pch.h"
#include "log.h"
#include "node_dialog_layer_rename.h"
#include "canvas.h"
#include "node_image_texture.h"
Node* NodeDialogLayerRename::clone_instantiate() const
{
return new NodeDialogLayerRename();
}
void NodeDialogLayerRename::clone_finalize(Node* dest) const
{
NodeDialogLayerRename* n = static_cast<NodeDialogLayerRename*>(dest);
n->init_controls();
}
void NodeDialogLayerRename::init()
{
auto tpl = static_cast<const NodeBorder*>(init_template("dialog-layer-rename"));
m_color = tpl->m_color;
m_border_color = tpl->m_border_color;;
m_thinkness = tpl->m_thinkness;;
init_controls();
}
void NodeDialogLayerRename::init_controls()
{
btn_ok = find<NodeButton>("btn-ok");
btn_cancel = find<NodeButton>("btn-cancel");
input = find<NodeTextInput>("txt-input");
btn_cancel->on_click = [this](Node*) {
destroy();
};
}
void NodeDialogLayerRename::loaded()
{
// ui::Image thumb = ui::Canvas::I->thumbnail_read(data_path);
// auto image_tex = find<NodeImageTexture>("thumb-tex");
// image_tex->tex.create(thumb);
}
std::string NodeDialogLayerRename::get_name()
{
return input ? input->m_string : "";
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "node_border.h"
#include "node_button.h"
#include "node_text_input.h"
class NodeDialogLayerRename : public NodeBorder
{
public:
NodeButton* btn_cancel;
NodeButton* btn_ok;
NodeTextInput* input;
std::string data_path;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
virtual void loaded() override;
std::string get_name();
};

239
src/node_dialog_open.cpp Normal file
View File

@@ -0,0 +1,239 @@
#include "pch.h"
#include "log.h"
#include "node_dialog_open.h"
#include "canvas.h"
#include "node_image_texture.h"
#include "asset.h"
#include "node_message_box.h"
#include "app.h"
Node* NodeDialogOpen::clone_instantiate() const
{
return new NodeDialogOpen();
}
void NodeDialogOpen::clone_finalize(Node* dest) const
{
NodeDialogOpen* n = static_cast<NodeDialogOpen*>(dest);
n->init_controls();
}
void NodeDialogOpen::init()
{
auto tpl = static_cast<const NodeBorder*>(init_template("dialog-open"));
m_color = tpl->m_color;
m_border_color = tpl->m_border_color;;
m_thinkness = tpl->m_thinkness;;
init_controls();
}
void NodeDialogOpen::init_controls()
{
btn_ok = find<NodeButton>("btn-ok");
btn_cancel = find<NodeButton>("btn-cancel");
btn_cancel->on_click = [this](Node*) {
destroy();
};
btn_delete = find<NodeButton>("btn-delete");
btn_delete->on_click = [this](Node*) {
if (!current)
return;
auto msgbox = new NodeMessageBox();
msgbox->m_manager = m_manager;
msgbox->init();
msgbox->m_title->set_text("Delete Project");
msgbox->m_message->set_text(("Are you sure you want to delete " + current->m_file_name + "?").c_str());
msgbox->btn_ok->on_click = [this,msgbox](Node*){
auto path = current->m_path;
int idx = container->get_child_index(current);
container->remove_child(current);
if (!container->m_children.empty())
{
int newidx = std::min<int>(idx, (int)container->m_children.size() - 1);
auto next = (NodeDialogOpenItem*)container->get_child_at(newidx);
current = nullptr;
next->on_selected(next);
next->m_selected = true;
}
else
{
current = nullptr;
auto image_tex = find<NodeImageTexture>("thumb-tex");
image_tex->tex.destroy();
}
Asset::delete_file(path);
msgbox->destroy();
};
root()->add_child(msgbox);
root()->update();
};
container = find<Node>("files-list");
auto names = Asset::list_files(data_path, false, ".*\\.pano");
for (const auto& n : names)
{
auto node = new NodeDialogOpenItem;
node->m_manager = m_manager;
node->init();
node->m_text->set_text(n.c_str());
node->m_path = data_path + "/" + n;
node->m_file_name = n;
node->on_selected = [&](NodeDialogOpenItem* target) {
if (target == current)
return;
ui::Image thumb = ui::Canvas::I->thumbnail_read(target->m_path);
auto image_tex = find<NodeImageTexture>("thumb-tex");
image_tex->tex.destroy();
image_tex->tex.create(thumb);
selected_path = target->m_path;
selected_file = target->m_file_name;
selected_name = selected_file.substr(0, selected_file.length() - 5);
if (current)
current->m_selected = false;
current = target;
};
container->add_child(node);
}
container->update();
if (auto* first = (NodeDialogOpenItem*)container->get_child_at(0))
{
first->on_selected(first);
first->m_selected = true;
}
}
void NodeDialogOpen::loaded()
{
}
//////////////////////////////////////////////////////////////////
Node* NodeDialogOpenItem::clone_instantiate() const
{
return new NodeDialogOpenItem;
}
void NodeDialogOpenItem::clone_finalize(Node* dest) const
{
NodeDialogOpenItem* n = static_cast<NodeDialogOpenItem*>(dest);
n->init_controls();
}
void NodeDialogOpenItem::init()
{
auto tpl = static_cast<const NodeBorder*>(init_template("dialog-open-item"));
m_color = tpl->m_color;
m_border_color = tpl->m_border_color;
m_thinkness = tpl->m_thinkness;
init_controls();
}
void NodeDialogOpenItem::init_controls()
{
m_text = find<NodeText>("title");
}
void NodeDialogOpenItem::loaded()
{
}
void NodeDialogOpenItem::draw()
{
auto c = m_selected ? m_color_selected : m_color_normal;
m_thinkness = m_selected ? 1.f : 0.f;
m_color = m_mouse_inside ? m_color_hover : c;
NodeBorder::draw();
}
kEventResult NodeDialogOpenItem::handle_event(Event* e)
{
NodeBorder::handle_event(e);
switch (e->m_type)
{
case kEventType::MouseEnter:
break;
case kEventType::MouseLeave:
break;
case kEventType::MouseDownL:
m_selected = true;
if (on_selected)
on_selected(this);
break;
case kEventType::MouseUpL:
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}
//////////////////////////////////////////////////////////////////
Node* NodeDialogSave::clone_instantiate() const
{
return new NodeDialogSave;
}
void NodeDialogSave::clone_finalize(Node* dest) const
{
NodeDialogSave* n = static_cast<NodeDialogSave*>(dest);
n->init_controls();
}
void NodeDialogSave::init()
{
auto tpl = static_cast<const NodeBorder*>(init_template("dialog-save"));
m_color = tpl->m_color;
m_border_color = tpl->m_border_color;
m_thinkness = tpl->m_thinkness;
init_controls();
}
void NodeDialogSave::init_controls()
{
btn_ok = find<NodeButton>("btn-ok");
btn_cancel = find<NodeButton>("btn-cancel");
btn_cancel->on_click = [this](Node*) {
destroy();
};
input = find<NodeTextInput>("txt-input");
input->on_return = [&](NodeTextInput* target){
if (btn_ok->on_click)
btn_ok->on_click(btn_ok);
};
}
void NodeDialogSave::loaded()
{
}
//////////////////////////////////////////////////////////////////
Node* NodeDialogNewDoc::clone_instantiate() const
{
return new NodeDialogNewDoc;
}
void NodeDialogNewDoc::clone_finalize(Node* dest) const
{
NodeDialogNewDoc* n = static_cast<NodeDialogNewDoc*>(dest);
n->init_controls();
}
void NodeDialogNewDoc::init()
{
auto tpl = static_cast<const NodeBorder*>(init_template("dialog-newdoc"));
m_color = tpl->m_color;
m_border_color = tpl->m_border_color;
m_thinkness = tpl->m_thinkness;
init_controls();
}
void NodeDialogNewDoc::init_controls()
{
btn_ok = find<NodeButton>("btn-ok");
m_resolution = find<NodeComboBox>("resolution");
btn_cancel = find<NodeButton>("btn-cancel");
btn_cancel->on_click = [this](Node*) {
destroy();
};
input = find<NodeTextInput>("txt-input");
input->on_return = [&](NodeTextInput* target){
if (btn_ok->on_click)
btn_ok->on_click(btn_ok);
};
}
void NodeDialogNewDoc::loaded()
{
}

75
src/node_dialog_open.h Normal file
View File

@@ -0,0 +1,75 @@
#pragma once
#include "node_border.h"
#include "node_button.h"
#include "node_image_texture.h"
#include "node_text.h"
#include "node_text_input.h"
#include "node_combobox.h"
class NodeDialogOpenItem : public NodeBorder
{
public:
NodeText* m_text;
NodeImageTexture* m_thumb;
glm::vec4 m_color_normal = glm::vec4(.4, .4, .4, 1);
glm::vec4 m_color_selected = glm::vec4(.3, .3, .3, 1);
glm::vec4 m_color_hover = glm::vec4(.5, .5, .5, 1);
bool m_selected = false;
std::string m_path;
std::string m_file_name;
std::function<void(NodeDialogOpenItem* target)> on_selected;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
virtual void loaded() override;
virtual void draw() override;
virtual kEventResult handle_event(Event* e) override;
};
class NodeDialogOpen : public NodeBorder
{
public:
NodeButton* btn_cancel;
NodeButton* btn_ok;
NodeButton* btn_delete;
NodeDialogOpenItem* current = nullptr;
Node* container;
std::string selected_path;
std::string selected_file;
std::string selected_name;
std::string data_path;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
virtual void loaded() override;
};
class NodeDialogSave : public NodeBorder
{
public:
NodeButton* btn_cancel;
NodeButton* btn_ok;
NodeTextInput* input;
std::string selected_path;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
virtual void loaded() override;
};
class NodeDialogNewDoc : public NodeBorder
{
public:
NodeButton* btn_cancel;
NodeButton* btn_ok;
NodeTextInput* input;
NodeComboBox* m_resolution;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
virtual void loaded() override;
};

View File

@@ -0,0 +1,93 @@
#include "pch.h"
#include "log.h"
#include "util.h"
#include "canvas.h"
#include "node_dialog_picker.h"
Node* NodeColorPicker::clone_instantiate() const
{
return new NodeColorPicker;
}
void NodeColorPicker::clone_finalize(Node* dest) const
{
auto n = static_cast<NodeColorPicker*>(dest);
n->init_controls();
}
void NodeColorPicker::init()
{
auto n = (NodeColorPicker*)init_template("color-picker");
n->clone_copy(this);
init_controls();
}
void NodeColorPicker::draw()
{
NodeBorder::draw();
// glm::vec3 rgb = glm::vec3(ui::Canvas::I->m_current_brush.m_tip_color);
// glm::vec3 hsv = convert_rgb2hsv(rgb);
// m_slider_h->m_value.x = hsv.x;
// m_slider_s->m_value.x = hsv.y;
// m_slider_v->m_value.x = hsv.z;
// m_slider_r->m_value.x = rgb.x;
// m_slider_g->m_value.x = rgb.y;
// m_slider_b->m_value.x = rgb.z;
// m_wheel->m_hsv = hsv;
}
void NodeColorPicker::handle_value_changed()
{
}
void NodeColorPicker::init_controls()
{
m_slider_h = find<NodeSliderH>("hsv-h");
m_slider_s = find<NodeSliderH>("hsv-s");
m_slider_v = find<NodeSliderH>("hsv-v");
m_slider_r = find<NodeSliderH>("rgb-r");
m_slider_g = find<NodeSliderH>("rgb-g");
m_slider_b = find<NodeSliderH>("rgb-b");
m_wheel = find<NodeColorWheel>("wheel");
m_color_cur = find<NodeBorder>("color-cur");
m_color_old = find<NodeBorder>("color-old");
m_color_old1 = find<NodeBorder>("color-old1");
m_color_old2 = find<NodeBorder>("color-old2");
m_button_select = find<NodeButton>("btn-select");
m_button_select->on_click = [this](Node*)
{
m_color_old2->m_color = m_color_old1->m_color;
m_color_old1->m_color = m_color_old->m_color;
m_color_old->m_color = m_color_cur->m_color;
};
m_wheel->on_value_changed = [this](Node*, glm::vec3 hsv)
{
m_slider_h->m_value.x = hsv.x;
m_slider_s->m_value.x = hsv.y;
m_slider_v->m_value.x = hsv.z;
glm::vec3 rgb = convert_hsv2rgb(hsv);
m_slider_r->m_value.x = rgb.x;
m_slider_g->m_value.x = rgb.y;
m_slider_b->m_value.x = rgb.z;
m_color_cur->m_color = {rgb,1};
};
auto hsv_setter = [this](Node* target, float v)
{
m_wheel->m_hsv = get_hsv();
glm::vec3 rgb = convert_hsv2rgb(get_hsv());
m_color_cur->m_color = {rgb,1};
};
m_slider_h->on_value_changed = hsv_setter;
m_slider_s->on_value_changed = hsv_setter;
m_slider_v->on_value_changed = hsv_setter;
}
glm::vec3 NodeColorPicker::get_hsv() const
{
float h = m_slider_h->get_value();
float s = m_slider_s->get_value();
float v = m_slider_v->get_value();
return glm::vec3(h, s, v);
}

33
src/node_dialog_picker.h Normal file
View File

@@ -0,0 +1,33 @@
#include "node.h"
#include "node_border.h"
#include "node_slider.h"
#include "node_colorwheel.h"
#include "node_button.h"
class NodeColorPicker : public NodeBorder
{
public:
NodeSliderH* m_slider_h;
NodeSliderH* m_slider_s;
NodeSliderH* m_slider_v;
NodeSliderH* m_slider_r;
NodeSliderH* m_slider_g;
NodeSliderH* m_slider_b;
NodeColorWheel* m_wheel;
NodeBorder* m_color_cur;
NodeBorder* m_color_old;
NodeBorder* m_color_old1;
NodeBorder* m_color_old2;
NodeButton* m_button_select;
glm::vec3 m_rgb;
glm::vec3 m_hsv;
virtual Node* clone_instantiate() const override;
//virtual void clone_copy(Node* dest) const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
virtual void draw() override;
void init_controls();
glm::vec3 get_hsv() const;
void handle_value_changed();
};

68
src/node_icon.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include "pch.h"
#include "log.h"
#include "node_icon.h"
#include "asset.h"
#include "texture.h"
std::map<std::string, glm::vec4> NodeIcon::m_icons;
void NodeIcon::static_init()
{
// spritesheet maker: https://draeton.github.io/stitches/
// icons: http://www.famfamfam.com/lab/icons/silk/
// regex css -> spritesheet.txt: \.([^{]+) {\s+width: (\d+)px;\s+height: (\d+)px;\s+.*: -(\d+)px -(\d+)px;\s+}\s+
// to: "\1",\2,\3,\4,\5\n
Asset file;
if (!(file.open("data/spritesheet.txt") && file.read_all()))
return;
char* data = (char*)file.m_data;
int size = file.m_len;
static char name[256];
int x, y, w, h;
char* s = strtok(data, "\n");
auto i = strlen(s) + 1;
while (i < size && sscanf(s, "%s %d %d %d %d", name, &w, &h, &x, &y) == 5)
{
m_icons[name] = glm::vec4(x, y, x + w, y + h);
s = strtok(nullptr, "\n");
i += strlen(s) + 1;
}
file.close();
TextureManager::load("data/spritesheet.png");
}
Node* NodeIcon::clone_instantiate() const
{
return new NodeIcon();
}
void NodeIcon::clone_copy(Node* dest) const
{
NodeImage::clone_copy(dest);
NodeIcon* n = static_cast<NodeIcon*>(dest);
n->m_icon_name = m_icon_name;
}
void NodeIcon::create()
{
m_region = m_icons[m_icon_name];
m_path = "data/spritesheet.png";
m_tex_id = const_hash(m_path.c_str());
m_use_atlas = true;
NodeImage::create();
auto tex_sz = TextureManager::get(m_tex_id).size();
YGNodeStyleSetAspectRatio(y_node, tex_sz.x / tex_sz.y);
}
void NodeIcon::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
NodeImage::parse_attributes(ka, attr);
switch (ka)
{
case kAttribute::Icon:
m_icon_name = attr->Value();
break;
default:
break;
}
}

14
src/node_icon.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include "node_image.h"
class NodeIcon : public NodeImage
{
static std::map<std::string, glm::vec4> m_icons;
std::string m_icon_name;
public:
static void static_init();
virtual Node* clone_instantiate() const override;
virtual void clone_copy(Node* dest) const override;
virtual void create() override;
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override;
};

103
src/node_image.cpp Normal file
View File

@@ -0,0 +1,103 @@
#include "pch.h"
#include "log.h"
#include "node_image.h"
#include "shader.h"
ui::Plane NodeImage::m_plane;
Sampler NodeImage::m_sampler;
Sampler NodeImage::m_sampler_mips;
void NodeImage::static_init()
{
m_plane.create<1>(1, 1);
m_sampler.create();
m_sampler_mips.create();
m_sampler_mips.set_filter(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);
}
Node* NodeImage::clone_instantiate() const
{
return new NodeImage();
}
void NodeImage::clone_copy(Node* dest) const
{
Node::clone_copy(dest);
NodeImage* n = static_cast<NodeImage*>(dest);
n->m_use_atlas = m_use_atlas;
n->m_region = m_region;
n->m_off = m_off;
n->m_sz = m_sz;
n->m_path = m_path;
n->m_tex_id = m_tex_id;
}
void NodeImage::create()
{
if (!m_path.empty() && TextureManager::load(m_path.c_str(), m_use_mipmaps))
{
//LOG("load image node %s", m_path.c_str());
auto tex_sz = TextureManager::get(m_tex_id).size();
m_off = xy(m_region) / tex_sz;
m_sz = (zw(m_region) - xy(m_region)) / tex_sz;
}
}
void NodeImage::restore_context()
{
Node::restore_context();
create();
}
void NodeImage::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
Node::parse_attributes(ka, attr);
switch (ka)
{
case kAttribute::Mips:
m_use_mipmaps = attr->BoolValue();
break;
case kAttribute::Path:
m_path = attr->Value();
m_tex_id = const_hash(attr->Value());
break;
case kAttribute::Region:
{
glm::vec4 v;
int n = sscanf(attr->Value(), "%f %f %f %f", &v.x, &v.y, &v.z, &v.w);
if (n == 4)
{
m_region = v;
m_use_atlas = true;
}
break;
}
default:
break;
}
}
void NodeImage::draw()
{
using namespace ui;
TextureManager::get(m_tex_id).bind();
auto& sampler = m_use_mipmaps ? m_sampler_mips : m_sampler;
sampler.bind(0);
glEnable(GL_BLEND);
if (m_use_atlas)
{
ui::ShaderManager::use(kShader::Atlas);
ui::ShaderManager::u_vec2(kShaderUniform::Tof, m_off);
ui::ShaderManager::u_vec2(kShaderUniform::Tsz, m_sz);
}
else
{
ui::ShaderManager::use(kShader::Texture);
}
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
m_plane.draw_fill();
sampler.unbind();
TextureManager::get(m_tex_id).unbind();
glDisable(GL_BLEND);
}

26
src/node_image.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include "node.h"
#include "shape.h"
#include "texture.h"
class NodeImage : public Node
{
public:
static ui::Plane m_plane;
static Sampler m_sampler;
static Sampler m_sampler_mips;
bool m_use_atlas = false;
bool m_use_mipmaps = false;
glm::vec4 m_region;
glm::vec2 m_off;
glm::vec2 m_sz;
std::string m_path;
uint16_t m_tex_id;
static void static_init();
virtual Node* clone_instantiate() const override;
virtual void clone_copy(Node* dest) const override;
virtual void create() override;
virtual void restore_context() override;
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override;
virtual void draw() override;
};

View File

@@ -0,0 +1,33 @@
#include "pch.h"
#include "log.h"
#include "node_image_texture.h"
#include "shader.h"
#include "node_image.h"
Node* NodeImageTexture::clone_instantiate() const
{
return new NodeImageTexture();
}
void NodeImageTexture::clone_copy(Node* dest) const
{
Node::clone_copy(dest);
NodeImageTexture* n = static_cast<NodeImageTexture*>(dest);
n->tex = tex;
}
void NodeImageTexture::draw()
{
using namespace ui;
tex.bind();
auto& sampler = tex.has_mips ? NodeImage::m_sampler_mips : NodeImage::m_sampler;
sampler.bind(0);
glEnable(GL_BLEND);
ui::ShaderManager::use(kShader::Texture);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
NodeImage::m_plane.draw_fill();
sampler.unbind();
tex.unbind();
glDisable(GL_BLEND);
}

18
src/node_image_texture.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include "node.h"
#include "texture.h"
class NodeImageTexture : public Node
{
public:
Texture2D tex;
virtual Node* clone_instantiate() const override;
virtual void clone_copy(Node* dest) const override;
// TODO: maybe we can save the texture data and restore later
//virtual void restore_context() override
//{
// Node::restore_context();
// create();
//}
virtual void draw() override;
};

25
src/node_message_box.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include "pch.h"
#include "log.h"
#include "node_message_box.h"
#include "layout.h"
Node* NodeMessageBox::clone_instantiate() const
{
return new NodeMessageBox();
}
void NodeMessageBox::init()
{
SetPosition(0, 0);
SetWidthP(100);
SetHeightP(100);
SetPositioning(YGPositionTypeAbsolute);
m_template = (*m_manager)[const_hash("message-box")]->m_children[0]->clone();
add_child(m_template);
m_title = m_template->find<NodeText>("title");
m_message = m_template->find<NodeText>("message");
btn_ok = m_template->find<NodeButton>("btn-ok");
btn_ok->on_click = [&](Node*) { destroy(); };
btn_cancel = m_template->find<NodeButton>("btn-cancel");
btn_cancel->on_click = [&](Node*) { destroy(); };
}

16
src/node_message_box.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "node.h"
#include "node_button.h"
#include "node_text.h"
class NodeMessageBox : public Node
{
public:
Node* m_template;
NodeButton* btn_ok;
NodeButton* btn_cancel;
NodeText* m_message;
NodeText* m_title;
virtual Node* clone_instantiate() const override;
virtual void init() override;
};

225
src/node_panel_brush.cpp Normal file
View File

@@ -0,0 +1,225 @@
#include "pch.h"
#include "log.h"
#include "node_panel_brush.h"
#include "asset.h"
#include "texture.h"
#ifdef __APPLE__
#include <Foundation/Foundation.h>
#endif
Node* NodeButtonBrush::clone_instantiate() const
{
return new NodeButtonBrush();
}
void NodeButtonBrush::init()
{
init_template("tpl-brush-icon");
color_hover = glm::vec4(.7, .7, .7, 1);
color_normal = glm::vec4(.3, .3, .3, 1);
m_color = color_normal;
img = (NodeImage*)m_children[0].get();
}
void NodeButtonBrush::set_icon(const char* path)
{
img->m_path = path;
img->m_tex_id = const_hash(img->m_path.c_str());
img->m_use_mipmaps = true;
img->create();
}
void NodeButtonBrush::draw()
{
m_color = m_mouse_inside ? color_hover : color_normal;
m_color = m_selected ? glm::vec4(.9, 0, 0, 1) : m_color;
NodeButtonCustom::draw();
}
Node* NodePanelBrush::clone_instantiate() const
{
return new NodePanelBrush();
}
void NodePanelBrush::init()
{
init_template("tpl-panel-brushes");
//m_layers_container = find<NodeBorder>("layers-container");
static auto icons = Asset::list_files("data/thumbs", true, ".*\\.png$");
if ((m_container = find<NodeBorder>("brushes")))
{
int count = 0;
for (auto& i : icons)
{
std::string path = "data/thumbs/" + i;
std::string path_hi = "data/brushes/" + i;
NodeButtonBrush* brush = new NodeButtonBrush;
m_container->add_child(brush);
brush->init();
brush->create();
brush->loaded();
brush->set_icon(path.c_str());
brush->m_brushID = count++;
brush->high_path = path_hi;
brush->brush_name = i;
brush->high_id = const_hash(path_hi.c_str());
m_brushes.push_back(brush);
brush->on_click = std::bind(&NodePanelBrush::handle_click, this, std::placeholders::_1);
}
}
}
void NodePanelBrush::handle_click(Node* target)
{
if (target == m_current)
return;
if (m_current)
m_current->m_selected = false;
m_current = (NodeButtonBrush*)target;
m_current->m_selected = true;
if (on_brush_changed)
on_brush_changed(this, m_current->m_brushID);
}
int NodePanelBrush::find_brush(const std::string & name) const
{
for (int i = 0; i < m_brushes.size(); i++)
{
if (m_brushes[i]->brush_name.find(name) != std::string::npos)
{
return i;
}
}
return -1;
}
uint16_t NodePanelBrush::get_texture_id(int index) const
{
TextureManager::load(m_brushes[index]->high_path.c_str(), true);
return m_brushes[index]->high_id;
}
int NodePanelBrush::get_brush_id(int index) const
{
return m_brushes[index]->m_brushID;
}
// select the current brush based on the texture id
void NodePanelBrush::select_brush(int brush_id)
{
if (m_current)
m_current->m_selected = false;
for (auto b : m_brushes)
{
if (b->m_brushID == brush_id)
{
b->m_selected = true;
m_current = b;
TextureManager::load(b->high_path.c_str(), true);
}
}
}
// -----------------------------------------------------------------------
Node* NodeBrushPresetItem::clone_instantiate() const
{
return new NodeBrushPresetItem();
}
void NodeBrushPresetItem::init()
{
init_template("tpl-brush-preset");
color_hover = glm::vec4(.7, .7, .7, 1);
color_normal = glm::vec4(.3, .3, .3, 1);
m_color = color_normal;
m_thumb = (NodeImage*)m_children[0].get();
m_preview = (NodeStrokePreview*)m_children[1].get();
}
void NodeBrushPresetItem::draw()
{
m_color = m_mouse_inside ? color_hover : color_normal;
m_color = m_selected ? glm::vec4(.9, 0, 0, 1) : m_color;
NodeButtonCustom::draw();
}
//---
Node* NodePanelBrushPreset::clone_instantiate() const
{
return new NodePanelBrushPreset();
}
void NodePanelBrushPreset::init()
{
init_template("tpl-panel-brush-preset");
static auto icons = Asset::list_files("data/thumbs", true, ".*\\.png$");
if ((m_container = find<NodeBorder>("brushes")))
{
int count = 0;
for (auto& i : icons)
{
std::string path = "data/thumbs/" + i;
std::string path_hi = "data/brushes/" + i;
NodeBrushPresetItem* brush = new NodeBrushPresetItem;
m_container->add_child(brush);
brush->init();
brush->create();
brush->loaded();
// brush->set_icon(path.c_str());
brush->m_brushID = count++;
brush->high_path = path_hi;
brush->high_id = const_hash(path_hi.c_str());
brush->m_brush.m_tex_id = const_hash(path.c_str());
brush->m_brush.m_tip_size = .05;
brush->m_brush.m_tip_flow = .2;
brush->m_brush.m_tip_opacity = 1;
brush->m_brush.m_tip_spacing = 0.03;
//brush->m_brush.m_jitter_spread = (rand() % 1000) * 0.0001;
brush->m_preview->m_brush = brush->m_brush;
brush->m_preview->draw_stroke();
brush->m_thumb->m_path = path;
brush->m_thumb->m_tex_id = const_hash(path.c_str());
brush->m_thumb->create();
m_brushes.push_back(brush);
brush->on_click = std::bind(&NodePanelBrushPreset::handle_click, this, std::placeholders::_1);
}
}
}
void NodePanelBrushPreset::handle_click(Node* target)
{
if (target == m_current)
return;
if (m_current)
m_current->m_selected = false;
m_current = (NodeButtonBrush*)target;
m_current->m_selected = true;
if (on_brush_changed)
on_brush_changed(this, m_current->m_brushID);
}
ui::Brush NodePanelBrushPreset::get_brush(int index) const
{
auto b = m_brushes[index]->m_brush;
TextureManager::load(m_brushes[index]->high_path.c_str(), true);
b.m_tex_id = m_brushes[index]->high_id;
return b;
}
uint16_t NodePanelBrushPreset::get_texture_id(int index) const
{
TextureManager::load(m_brushes[index]->high_path.c_str(), true);
return m_brushes[index]->high_id;
}
int NodePanelBrushPreset::get_brush_id(int index) const
{
return m_brushes[index]->m_brushID;
}

71
src/node_panel_brush.h Normal file
View File

@@ -0,0 +1,71 @@
#pragma once
#include "node.h"
#include "node_button_custom.h"
#include "node_image.h"
#include "node_stroke_preview.h"
#include "brush.h"
class NodeButtonBrush : public NodeButtonCustom
{
public:
int m_brushID;
bool m_selected = false;
std::string brush_name;
std::string high_path;
uint16_t high_id;
NodeImage* img;
virtual Node* clone_instantiate() const override;
virtual void init() override;
void set_icon(const char* path);
virtual void draw() override;
};
class NodePanelBrush : public Node
{
std::vector<NodeButtonBrush*> m_brushes;
NodeButtonBrush* m_current = nullptr;
Node* m_container;
public:
std::function<void(Node* target, int id)> on_brush_changed;
virtual Node* clone_instantiate() const override;
virtual void init() override;
void handle_click(Node* target);
std::vector<std::string> FindAllBrushes(const std::string& folder);
int find_brush(const std::string& name) const;
uint16_t get_texture_id(int index) const;
int get_brush_id(int index) const;
void select_brush(int brush_id);
};
// -----------------------------------------------------------------------
class NodeBrushPresetItem : public NodeButtonCustom
{
public:
int m_brushID;
ui::Brush m_brush;
std::string high_path;
uint16_t high_id;
bool m_selected = false;
NodeStrokePreview* m_preview;
NodeImage* m_thumb;
virtual Node* clone_instantiate() const override;
virtual void init() override;
virtual void draw() override;
};
class NodePanelBrushPreset : public Node
{
std::vector<NodeBrushPresetItem*> m_brushes;
NodeButtonBrush* m_current = nullptr;
Node* m_container;
public:
std::function<void(Node* target, int id)> on_brush_changed;
virtual Node* clone_instantiate() const override;
virtual void init() override;
void handle_click(Node* target);
uint16_t get_texture_id(int index) const;
ui::Brush get_brush(int index) const;
int get_brush_id(int index) const;
};

59
src/node_panel_color.cpp Normal file
View File

@@ -0,0 +1,59 @@
#include "pch.h"
#include "log.h"
#include "node_panel_color.h"
#include "canvas.h"
Node* NodePanelColor::clone_instantiate() const
{
return new NodePanelColor();
}
void NodePanelColor::clone_finalize(Node* dest) const
{
NodePanelColor* n = static_cast<NodePanelColor*>(dest);
n->init_controls();
}
void NodePanelColor::init()
{
init_template("tpl-panel-color");
init_controls();
}
void NodePanelColor::init_controls()
{
m_quad = find<NodeColorQuad>("quad");
m_hue = find<NodeSliderHue>("hue");
m_hue->on_hue_changed = [this](Node*, glm::vec4 hue_color) {
m_base_color = m_quad->m_color = hue_color;
float hue = convert_rgb2hsv(m_base_color).x;
m_color = glm::vec4(convert_hsv2rgb(glm::vec3(hue, m_cursor.x, 1.f-m_cursor.y)), 1.f);
m_quad->m_color = hue_color;
if (on_color_changed)
on_color_changed(this, m_color);
};
m_quad->on_value_changed = [this](Node*, glm::vec2 pos)
{
m_cursor = pos;
float hue = convert_rgb2hsv(m_base_color).x;
m_color = glm::vec4(convert_hsv2rgb(glm::vec3(hue, m_cursor.x, 1.f-m_cursor.y)), 1.f);
if (on_color_changed)
on_color_changed(this, m_color);
};
m_hue->set_value(0);
}
void NodePanelColor::set_color(glm::vec3 rgb)
{
auto hsv = convert_rgb2hsv(rgb);
m_hue->m_value.y = hsv.x;
m_quad->m_value = glm::vec2(hsv.y, 1.f - hsv.z);
m_quad->m_color = glm::vec4(rgb, 1);
m_cursor = m_quad->m_value;
m_base_color = glm::vec4(rgb, 1);
}
void NodePanelColor::added(Node* parent)
{
set_color(ui::Canvas::I->m_current_brush.m_tip_color);
}

21
src/node_panel_color.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include "node.h"
#include "node_color_quad.h"
#include "node_slider.h"
class NodePanelColor : public Node
{
public:
NodeColorQuad* m_quad;
NodeSliderHue* m_hue;
glm::vec4 m_base_color;
glm::vec4 m_color;
glm::vec2 m_cursor;
std::function<void(Node* target, glm::vec4 color)> on_color_changed;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
virtual void added(Node* parent) override;
void init_controls();
void set_color(glm::vec3 rgb);
};

65
src/node_panel_grid.cpp Normal file
View File

@@ -0,0 +1,65 @@
#include "pch.h"
#include "log.h"
#include "node_panel_grid.h"
#include "canvas.h"
#include "app.h"
#include "image.h"
Node* NodePanelGrid::clone_instantiate() const
{
return new NodePanelGrid();
}
void NodePanelGrid::clone_finalize(Node* dest) const
{
NodePanelGrid* n = static_cast<NodePanelGrid*>(dest);
n->init_controls();
}
void NodePanelGrid::init()
{
init_template("tpl-panel-grid");
init_controls();
}
void NodePanelGrid::init_controls()
{
m_groud_opacity = find<NodeSliderH>("grid-ground-opacity");
m_groud_scale = find<NodeSliderH>("grid-ground-scale");
m_groud_value = find<NodeSliderH>("grid-ground-value");
m_groud_height = find<NodeSliderH>("grid-ground-height");
//m_box_opacity = find<NodeSliderH>("grid-box-opacity");
//m_box_width = find<NodeSliderH>("grid-box-width");
//m_box_height = find<NodeSliderH>("grid-box-height");
//m_box_depth = find<NodeSliderH>("grid-box-depth");
auto update_hm = [this](Node* target, float v) {
m_hm_plane.create(1, 1, m_hm_image, -m_hm_height->get_value());
};
m_hm_preview = find<NodeImageTexture>("grid-heightmap-preview");
m_hm_load = find<NodeButton>("grid-heightmap-load");
m_hm_offset = find<NodeSliderH>("grid-heightmap-offset");
m_hm_height = find<NodeSliderH>("grid-heightmap-height");
m_hm_height->on_value_changed = update_hm;
m_hm_preview->SetHeight(0);
//m_hm_plane.create(1, 1);
m_hm_load->on_click = [this](Node*) {
App::I.pick_image([this](std::string path) {
ui::Image img;
if (img.load_file(path))
{
m_hm_image = img.resize(128, 128);
m_hm_preview->tex.create(m_hm_image);
m_hm_preview->tex.create_mipmaps();
auto sz = m_hm_preview->tex.size();
m_hm_preview->SetAspectRatio(sz.x / sz.y);
m_hm_plane.create(1, 1, m_hm_image, -m_hm_height->get_value());
m_hm_preview->SetHeight(100);
}
});
};
}

35
src/node_panel_grid.h Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#include "node.h"
#include "node_stroke_preview.h"
#include "node_slider.h"
#include "brush.h"
#include "node_checkbox.h"
#include "node_combobox.h"
#include "node_image_texture.h"
#include "node_button.h"
#include "shape.h"
#include "image.h"
class NodePanelGrid : public Node
{
public:
NodeSliderH* m_groud_opacity;
NodeSliderH* m_groud_scale;
NodeSliderH* m_groud_value;
NodeSliderH* m_groud_height;
NodeSliderH* m_box_opacity;
NodeSliderH* m_box_width;
NodeSliderH* m_box_height;
NodeSliderH* m_box_depth;
NodeImageTexture* m_hm_preview;
NodeButton* m_hm_load;
NodeSliderH* m_hm_offset;
NodeSliderH* m_hm_height;
ui::HeightmapPlane m_hm_plane;
ui::Image m_hm_image;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
};

245
src/node_panel_layer.cpp Normal file
View File

@@ -0,0 +1,245 @@
#include "pch.h"
#include "log.h"
#include "node_panel_layer.h"
Node* NodeLayer::clone_instantiate() const
{
return new NodeLayer();
}
void NodeLayer::clone_children(Node* dest) const
{
NodeBorder::clone_children(dest);
NodeLayer* n = static_cast<NodeLayer*>(dest);
n->m_label = n->find<NodeText>("label");
n->m_visibility = n->find<NodeCheckBox>("cb");
}
void NodeLayer::clone_copy(Node* dest) const
{
NodeBorder::clone_copy(dest);
NodeLayer* n = (NodeLayer*)dest;
n->m_selected = m_selected;
n->m_label_text = m_label_text;
}
void NodeLayer::init()
{
const auto& m_template = (NodeBorder*)init_template("tpl-layer");
m_color = m_template->m_color;
m_border_color = m_template->m_border_color;
m_thinkness = m_template->m_thinkness;
m_label = find<NodeText>("label");
m_visibility = find<NodeCheckBox>("cb");
m_opacity = find<NodeSliderH>("sl-opacity");
}
void NodeLayer::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
NodeBorder::parse_attributes(ka, attr);
switch (ka)
{
case kAttribute::Text:
m_label_text = attr->Value();
break;
case kAttribute::Selected:
m_selected = attr->BoolValue();
default:
break;
}
}
void NodeLayer::loaded()
{
NodeBorder::loaded();
if (!m_label_text.empty())
m_label->set_text(m_label_text.c_str());
m_opacity->on_value_changed = [this](Node*, float value) {
if (on_opacity_changed)
on_opacity_changed(this, value);
};
m_visibility->on_value_changed = [this](Node*, bool checked) {
if (on_visibility_changed)
on_visibility_changed(this, checked);
};
}
kEventResult NodeLayer::handle_event(Event* e)
{
NodeBorder::handle_event(e);
switch (e->m_type)
{
case kEventType::MouseEnter:
break;
case kEventType::MouseLeave:
break;
case kEventType::MouseDownL:
m_selected = true;
if (on_selected)
on_selected(this);
if (on_highlight)
on_highlight(this, true);
mouse_capture();
break;
case kEventType::MouseUpL:
if (on_highlight)
on_highlight(this, false);
mouse_release();
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}
void NodeLayer::draw()
{
auto c = m_selected ? m_color_selected : m_color_normal;
m_thinkness = m_selected ? 1.f : 0.f;
m_color = m_mouse_inside ? m_color_hover : c;
NodeBorder::draw();
}
void NodeLayer::set_name(const char* s)
{
m_label_text = s;
m_label->set_text(s);
}
Node* NodePanelLayer::clone_instantiate() const
{
return new NodePanelLayer();
}
void NodePanelLayer::init()
{
LOG("NodePanelLayer::init");
init_template("tpl-panel-layers");
LOG("template initted");
m_layers_container = find<NodeBorder>("layers-container");
LOG("template container found");
// for (int i = 0; i < 1; i++)
// {
// LOG("add layer");
// add_layer();
// }
LOG("find components");
// m_current_layer = m_layers[0];
// m_layers[0]->m_selected = true;
btn_add = find<NodeButtonCustom>("btn-add");
btn_remove = find<NodeButtonCustom>("btn-remove");
btn_up = find<NodeButtonCustom>("btn-up");
btn_down = find<NodeButtonCustom>("btn-down");
LOG("attach events");
btn_add->on_click = [this](Node*) {
add_layer();
if (on_layer_add)
on_layer_add(this);
};
btn_remove->on_click = [this](Node*) {
if (m_layers.size() == 1)
return; // dont' delete the last layer
remove_layer(m_current_layer);
};
btn_up->on_click = [this](Node*) {
int old_idx = m_layers_container->get_child_index(m_current_layer);
m_layers_container->move_child_offset(m_current_layer, -1);
int new_idx = m_layers_container->get_child_index(m_current_layer);
if (on_layer_order && old_idx != new_idx)
{
on_layer_order(this, old_idx, new_idx);
}
};
btn_down->on_click = [this](Node*) {
int old_idx = m_layers_container->get_child_index(m_current_layer);
m_layers_container->move_child_offset(m_current_layer, +1);
int new_idx = m_layers_container->get_child_index(m_current_layer);
if (on_layer_order && old_idx != new_idx)
{
on_layer_order(this, old_idx, new_idx);
}
};
LOG("done init");
}
void NodePanelLayer::add_layer(const char* name)
{
NodeLayer* l = new NodeLayer;
m_layers_container->add_child(l);
l->init();
l->create();
l->loaded();
l->set_name(name);
l->on_selected = std::bind(&NodePanelLayer::handle_layer_selected, this, std::placeholders::_1);
l->on_opacity_changed = std::bind(&NodePanelLayer::handle_layer_opacity, this, std::placeholders::_1, std::placeholders::_2);
l->on_visibility_changed = std::bind(&NodePanelLayer::handle_layer_visibility, this, std::placeholders::_1, std::placeholders::_2);
l->on_highlight = std::bind(&NodePanelLayer::handle_layer_highlight, this, std::placeholders::_1, std::placeholders::_2);
if (m_current_layer)
m_current_layer->m_selected = false;
m_current_layer = l;
m_current_layer->m_selected = true;
m_layers.push_back(l);
}
void NodePanelLayer::add_layer()
{
static char s[64];
sprintf(s, "Layer-%d", id_counter++);
add_layer(s);
}
NodeLayer* NodePanelLayer::get_layer_at(int index)
{
return static_cast<NodeLayer*>(m_layers_container->get_child_at(index));
}
void NodePanelLayer::remove_layer(NodeLayer* layer)
{
auto it = std::find(m_layers.begin(), m_layers.end(), m_current_layer);
auto i = m_layers_container->get_child_index(m_current_layer);
int old_idx = (int)std::distance(m_layers.begin(), it);
m_layers_container->remove_child(m_current_layer);
m_layers.erase(it);
i = std::min<int>(i, (int)m_layers.size() - 1);
m_current_layer = m_layers[i];
m_current_layer->m_selected = true;
if (on_layer_delete)
on_layer_delete(this, old_idx);
if (on_layer_change)
on_layer_change(this, -1, i);
}
void NodePanelLayer::handle_layer_opacity(NodeLayer* target, float value)
{
if (on_layer_opacity_changed)
on_layer_opacity_changed(this, m_layers_container->get_child_index(target), value);
}
void NodePanelLayer::handle_layer_highlight(NodeLayer* target, bool highlight)
{
if (on_layer_highlight_changed)
on_layer_highlight_changed(this, m_layers_container->get_child_index(target), highlight);
}
void NodePanelLayer::handle_layer_visibility(NodeLayer* target, bool visible)
{
if (on_layer_visibility_changed)
on_layer_visibility_changed(this, m_layers_container->get_child_index(target), visible);
}
void NodePanelLayer::handle_layer_selected(NodeLayer* target)
{
if (m_current_layer)
m_current_layer->m_selected = false;
m_current_layer = target;
m_current_layer->m_selected = true;
if (on_layer_change)
on_layer_change(this, -1, m_layers_container->get_child_index(m_current_layer));
}
void NodePanelLayer::clear()
{
m_layers_container->remove_all_children();
m_layers.clear();
m_current_layer = nullptr;
}

63
src/node_panel_layer.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
#include "node_border.h"
#include "node_text.h"
#include "node_checkbox.h"
#include "node_slider.h"
#include "node_button_custom.h"
class NodeLayer : public NodeBorder
{
public:
std::function<void(NodeLayer* target)> on_selected;
std::function<void(NodeLayer* target, float opacity)> on_opacity_changed;
std::function<void(NodeLayer* target, bool visible)> on_visibility_changed;
std::function<void(NodeLayer* target, bool highlight)> on_highlight;
bool m_selected = false;
glm::vec4 m_color_normal = glm::vec4(.4, .4, .4, 1);
glm::vec4 m_color_selected = glm::vec4(.3, .3, .3, 1);
glm::vec4 m_color_hover = glm::vec4(.5, .5, .5, 1);
std::string m_label_text;
NodeText* m_label;
NodeCheckBox* m_visibility;
NodeSliderH* m_opacity;
virtual Node* clone_instantiate() const override;
virtual void clone_children(Node* dest) const override;
virtual void clone_copy(Node* dest) const override;
virtual void init() override;
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override;
virtual void loaded() override;
virtual kEventResult handle_event(Event* e) override;
virtual void draw() override;
void set_name(const char* s);
};
class NodePanelLayer : public Node
{
NodeButtonCustom* btn_add;
NodeButtonCustom* btn_remove;
NodeButtonCustom* btn_up;
NodeButtonCustom* btn_down;
int id_counter = 0;
public:
std::function<void(Node* target, int old_idx, int new_idx)> on_layer_change;
std::function<void(Node* target, int idx, float value)> on_layer_opacity_changed;
std::function<void(Node* target, int idx, bool visible)> on_layer_visibility_changed;
std::function<void(Node* target, int idx, bool highlight)> on_layer_highlight_changed;
std::function<void(Node* target, int index)> on_layer_delete;
std::function<void(Node* target)> on_layer_add;
std::function<void(Node* target, int old_idx, int new_idx)> on_layer_order;
NodeLayer* m_current_layer = nullptr;
std::vector<NodeLayer*> m_layers;
NodeBorder* m_layers_container;
virtual Node* clone_instantiate() const override;
virtual void init() override;
void add_layer();
void add_layer(const char* name);
NodeLayer* get_layer_at(int index);
void remove_layer(NodeLayer* layer);
void handle_layer_opacity(NodeLayer* target, float value);
void handle_layer_visibility(NodeLayer* target, bool visible);
void handle_layer_highlight(NodeLayer* target, bool highlight);
void handle_layer_selected(NodeLayer* target);
void clear();
};

123
src/node_panel_stroke.cpp Normal file
View File

@@ -0,0 +1,123 @@
#include "pch.h"
#include "log.h"
#include "node_panel_stroke.h"
#include "canvas.h"
Node* NodePanelStroke::clone_instantiate() const
{
return new NodePanelStroke();
}
void NodePanelStroke::clone_finalize(Node* dest) const
{
NodePanelStroke* n = static_cast<NodePanelStroke*>(dest);
n->init_controls();
}
void NodePanelStroke::init()
{
init_template("tpl-panel-stroke");
init_controls();
}
void NodePanelStroke::update_controls()
{
const auto& b = ui::Canvas::I->m_current_brush;
m_tip_size->m_value.x = glm::pow(b.m_tip_size, 1.f/3.f);
m_tip_spacing->m_value.x = glm::pow(b.m_tip_spacing, 1.f/2.f) / 4.f;
m_tip_flow->m_value.x = glm::pow(b.m_tip_flow, 1.f/2.f);
m_tip_opacity->m_value.x = b.m_tip_opacity;
m_tip_angle->m_value.x = b.m_tip_angle;
m_tip_stencil->m_value.x = b.m_tip_stencil;
m_tip_wet->m_value.x = b.m_tip_wet;
m_tip_noise->m_value.x = b.m_tip_noise;
m_jitter_scale->m_value.x = b.m_jitter_scale;
m_jitter_angle->m_value.x = b.m_jitter_angle;
m_jitter_spread->m_value.x = b.m_jitter_spread;
m_jitter_flow->m_value.x = b.m_jitter_flow;
m_jitter_hue->m_value.x = b.m_jitter_hue;
m_jitter_sat->m_value.x = b.m_jitter_sat;
m_jitter_val->m_value.x = b.m_jitter_val;
m_tip_angle_follow->checked = b.m_tip_angle_follow;
m_tip_flow_pressure->checked = b.m_tip_flow_pressure;
m_tip_size_pressure->checked = b.m_tip_size_pressure;
m_preview->m_brush = b;
m_preview->draw_stroke();
}
void NodePanelStroke::init_controls()
{
m_preview = find<NodeStrokePreview>("canvas");
m_blend_mode = find<NodeComboBox>("blend-mode");
m_blend_mode->on_select = [](Node*, int index) {
ui::Canvas::I->m_current_brush.m_blend_mode = index;
};
init_slider(m_tip_size, "tip-size", &ui::Brush::m_tip_size);
init_slider(m_tip_spacing, "tip-spacing", &ui::Brush::m_tip_spacing);
init_slider(m_tip_flow, "tip-flow", &ui::Brush::m_tip_flow);
init_slider(m_tip_opacity, "tip-opacity", &ui::Brush::m_tip_opacity);
init_slider(m_tip_angle, "tip-angle", &ui::Brush::m_tip_angle);
init_slider(m_tip_mix, "tip-mix", &ui::Brush::m_tip_mix);
init_slider(m_tip_stencil, "tip-stencil", &ui::Brush::m_tip_stencil);
init_slider(m_tip_wet, "tip-wet", &ui::Brush::m_tip_wet);
init_slider(m_tip_noise, "tip-noise", &ui::Brush::m_tip_noise);
init_slider(m_tip_hue, "tip-hue", &ui::Brush::m_tip_hue);
init_slider(m_tip_sat, "tip-sat", &ui::Brush::m_tip_sat);
init_slider(m_tip_val, "tip-val", &ui::Brush::m_tip_val);
init_slider(m_jitter_scale, "jitter-scale", &ui::Brush::m_jitter_scale);
init_slider(m_jitter_angle, "jitter-angle", &ui::Brush::m_jitter_angle);
init_slider(m_jitter_spread, "jitter-spread", &ui::Brush::m_jitter_spread);
init_slider(m_jitter_flow, "jitter-flow", &ui::Brush::m_jitter_flow);
init_slider(m_jitter_hue, "jitter-hue", &ui::Brush::m_jitter_hue);
init_slider(m_jitter_sat, "jitter-sat", &ui::Brush::m_jitter_sat);
init_slider(m_jitter_val, "jitter-val", &ui::Brush::m_jitter_val);
m_curves[m_tip_size] = [](float v){ return glm::pow(v, 3.f); };
m_curves[m_tip_spacing] = [](float v){ return glm::pow(v * 4.f, 2.f); };
m_curves[m_tip_flow] = [](float v){ return glm::pow(v, 2.f); };
init_checkbox(m_tip_angle_follow, "tip-angle-follow", &ui::Brush::m_tip_angle_follow);
init_checkbox(m_tip_flow_pressure, "tip-flow-pressure", &ui::Brush::m_tip_flow_pressure);
init_checkbox(m_tip_size_pressure, "tip-size-pressure", &ui::Brush::m_tip_size_pressure);
init_checkbox(m_tip_hue_pressure, "tip-hue-pressure", &ui::Brush::m_tip_hue_pressure);
init_checkbox(m_tip_sat_pressure, "tip-sat-pressure", &ui::Brush::m_tip_sat_pressure);
init_checkbox(m_tip_val_pressure, "tip-val-pressure", &ui::Brush::m_tip_val_pressure);
m_preview->m_brush = ui::Canvas::I->m_current_brush;
m_preview->draw_stroke();
}
void NodePanelStroke::init_slider(NodeSliderH*& target, const char* id, float ui::Brush::* prop)
{
target = find<NodeSliderH>(id);
target->on_value_changed = std::bind(&NodePanelStroke::handle_slide,
this, prop, std::placeholders::_1, std::placeholders::_2);
//m_canvas->m_brush.*prop = target->m_value.x;
}
void NodePanelStroke::handle_slide(float ui::Brush::* prop, Node* target, float value)
{
auto curve = m_curves.find((NodeSliderH*)target);
ui::Canvas::I->m_current_brush.*prop = curve != m_curves.end() ? curve->second(value) : value;
m_preview->m_brush = ui::Canvas::I->m_current_brush;
m_preview->draw_stroke();
if (on_stroke_change)
on_stroke_change(this);
}
void NodePanelStroke::init_checkbox(NodeCheckBox*& target, const char* id, bool ui::Brush::* prop)
{
target = find<NodeCheckBox>(id);
target->on_value_changed = std::bind(&NodePanelStroke::handle_checkbox,
this, prop, std::placeholders::_1, std::placeholders::_2);
ui::Canvas::I->m_current_brush.*prop = target->checked;
}
void NodePanelStroke::handle_checkbox(bool ui::Brush::* prop, Node *target, bool value)
{
ui::Canvas::I->m_current_brush.*prop = value;
m_preview->m_brush = ui::Canvas::I->m_current_brush;
m_preview->draw_stroke();
if (on_stroke_change)
on_stroke_change(this);
}

53
src/node_panel_stroke.h Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include "node.h"
#include "node_stroke_preview.h"
#include "node_slider.h"
#include "brush.h"
#include "node_checkbox.h"
#include "node_combobox.h"
class NodePanelStroke : public Node
{
public:
NodeStrokePreview* m_preview;
NodeComboBox* m_blend_mode;
NodeSliderH* m_tip_size;
NodeSliderH* m_tip_spacing;
NodeSliderH* m_tip_flow;
NodeSliderH* m_tip_opacity;
NodeSliderH* m_tip_angle;
NodeSliderH* m_tip_mix;
NodeSliderH* m_tip_stencil;
NodeSliderH* m_tip_wet;
NodeSliderH* m_tip_noise;
NodeSliderH* m_tip_hue;
NodeSliderH* m_tip_sat;
NodeSliderH* m_tip_val;
NodeSliderH* m_jitter_scale;
NodeSliderH* m_jitter_angle;
NodeSliderH* m_jitter_spread;
NodeSliderH* m_jitter_flow;
NodeSliderH* m_jitter_hue;
NodeSliderH* m_jitter_sat;
NodeSliderH* m_jitter_val;
NodeCheckBox* m_tip_angle_follow;
NodeCheckBox* m_tip_flow_pressure;
NodeCheckBox* m_tip_size_pressure;
NodeCheckBox* m_tip_hue_pressure;
NodeCheckBox* m_tip_sat_pressure;
NodeCheckBox* m_tip_val_pressure;
std::function<void(Node* target)> on_stroke_change;
std::map<NodeSliderH*, std::function<float(float)>> m_curves;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
void update_controls();
void init_slider(NodeSliderH*& slider, const char* id, float ui::Brush::* prop);
void handle_slide(float ui::Brush::* prop, Node* target, float value);
void init_checkbox(NodeCheckBox*& slider, const char* id, bool ui::Brush::* prop);
void handle_checkbox(bool ui::Brush::* prop, Node* target, bool value);
};

52
src/node_popup_menu.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include "pch.h"
#include "log.h"
#include "node_popup_menu.h"
Node* NodePopupMenu::clone_instantiate() const
{
return new NodePopupMenu();
}
void NodePopupMenu::init()
{
m_flood_events = true;
SetPosition(0, 0);
SetWidth(100);
SetHeight(500);
SetPositioning(YGPositionTypeAbsolute);
m_mouse_ignore = false;
m_capture_children = false;
}
kEventResult NodePopupMenu::handle_event(Event* e)
{
switch (e->m_type)
{
case kEventType::MouseDownL:
break;
case kEventType::MouseUpL:
if (!m_mouse_inside)
{
mouse_release();
}
else
{
for (int i = 0; i < m_children.size(); i++)
{
if (m_children[i]->m_mouse_inside)
{
if (on_select)
on_select(this, i);
break;
}
}
mouse_release();
}
destroy();
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}

11
src/node_popup_menu.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include "node.h"
class NodePopupMenu : public Node
{
public:
std::function<void(Node* target, int index)> on_select;
virtual Node* clone_instantiate() const override;
virtual void init() override;
virtual kEventResult handle_event(Event* e) override;
};

24
src/node_progress_bar.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "pch.h"
#include "log.h"
#include "node_progress_bar.h"
#include "layout.h"
Node* NodeProgressBar::clone_instantiate() const
{
return new NodeProgressBar();
}
void NodeProgressBar::init()
{
auto tpl = static_cast<const NodeBorder*>(init_template("progress-bar"));
m_color = tpl->m_color;
m_border_color = tpl->m_border_color;
m_thinkness = tpl->m_thinkness;
m_title = find<NodeText>("title");
btn_cancel = find<NodeButton>("btn-cancel");
btn_cancel->on_click = [&](Node*) { destroy(); };
m_progress = find<NodeBorder>("progress");
m_progress->SetWidthP(10);
}

16
src/node_progress_bar.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "node.h"
#include "node_button.h"
#include "node_text.h"
#include "node_border.h"
class NodeProgressBar : public NodeBorder
{
public:
Node* m_template;
NodeButton* btn_cancel;
NodeText* m_title;
NodeBorder* m_progress;
virtual Node* clone_instantiate() const override;
virtual void init() override;
};

69
src/node_scroll.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include "pch.h"
#include "log.h"
#include "node_scroll.h"
#include "event.h"
Node* NodeScroll::clone_instantiate() const
{
return new NodeScroll;
}
void NodeScroll::fix_scroll()
{
auto pad = GetPadding();
glm::vec2 padoff = { pad.y + pad.w, pad.x + pad.z };
auto rect = get_children_rect();
m_offset = glm::clamp(m_offset, - zw(rect) + zw(m_clip_uncut) - padoff, { 0, 0 });
m_pos_offset_childred = m_offset;
}
kEventResult NodeScroll::handle_event(Event* e)
{
NodeBorder::handle_event(e);
auto me = static_cast<MouseEvent*>(e);
auto ge = static_cast<GestureEvent*>(e);
auto loc = (me->m_pos - m_pos) * root()->m_zoom;
switch (e->m_type)
{
case kEventType::MouseDownL:
m_dragging = true;
m_drag_start = me->m_pos;
m_offset_start = m_offset;
mouse_capture();
break;
case kEventType::MouseMove:
if (m_dragging)
{
m_offset = m_offset_start + (me->m_pos - m_drag_start) * m_mask;
fix_scroll();
}
break;
case kEventType::MouseUpL:
mouse_release();
m_dragging = false;
break;
case kEventType::MouseScroll:
m_offset += me->m_scroll_delta * 50;
fix_scroll();
break;
case kEventType::GestureStart:
m_offset_start = m_offset;
mouse_capture();
break;
case kEventType::GestureMove:
m_offset = m_offset_start + ge->m_pos_delta * m_mask;
fix_scroll();
break;
case kEventType::GestureEnd:
mouse_release();
break;
case kEventType::MouseCancel:
mouse_release();
m_dragging = false;
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}

15
src/node_scroll.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include "node_border.h"
class NodeScroll : public NodeBorder
{
bool m_dragging = false;
glm::vec2 m_drag_start;
glm::vec2 m_offset_start;
glm::vec2 m_offset;
glm::vec2 m_mask{ 0, 1 };
public:
virtual Node* clone_instantiate() const override;
virtual kEventResult handle_event(Event* e) override;
void fix_scroll();
};

26
src/node_settings.cpp Normal file
View File

@@ -0,0 +1,26 @@
#include "pch.h"
#include "log.h"
#include "node_settings.h"
#include "layout.h"
Node* NodeSettings::clone_instantiate() const
{
return new NodeSettings();
}
void NodeSettings::init()
{
SetPosition(0, 0);
SetWidthP(100);
SetHeightP(100);
SetPositioning(YGPositionTypeAbsolute);
m_template = (*m_manager)[const_hash("settings")]->m_children[0]->clone();
add_child(m_template);
btnOk = m_template->find<NodeButton>("btn-ok");
btnOk->on_click = [&](Node*) { destroy(); };
}
kEventResult NodeSettings::handle_event(Event* e)
{
return kEventResult::Consumed;
}

13
src/node_settings.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include "node.h"
#include "node_button.h"
class NodeSettings : public Node
{
Node* m_template;
NodeButton* btnOk;
public:
virtual Node* clone_instantiate() const override;
virtual void init() override;
virtual kEventResult handle_event(Event* e) override;
};

160
src/node_slider.cpp Normal file
View File

@@ -0,0 +1,160 @@
#include "pch.h"
#include "log.h"
#include "node_slider.h"
#include "shader.h"
Node* NodeSliderH::clone_instantiate() const
{
return new NodeSliderH();
}
void NodeSliderH::clone_copy(Node* dest) const
{
NodeBorder::clone_copy(dest);
NodeSliderH* n = static_cast<NodeSliderH*>(dest);
n->m_value = m_value;
}
void NodeSliderH::init()
{
SetPadding(1, 1, 1, 1);
SetWidthP(100);
SetHeightP(100);
m_color = glm::vec4(1);
}
void NodeSliderH::draw()
{
NodeBorder::draw();
using namespace ui;
auto sz = GetSize();
glm::vec2 cur_size = sz * (1.f - m_mask) + m_mask * glm::vec2(10);
glm::mat4 scale = glm::scale(glm::vec3(cur_size, 1.f));
glm::mat4 pos = glm::translate(glm::vec3(m_value * m_mask * sz + m_pos + sz * .5f * (1.f - m_mask), 0));
auto mvp = m_proj * pos * scale;
ui::ShaderManager::use(kShader::Color);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, mvp);
ui::ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 });
m_plane.draw_fill();
}
void NodeSliderH::set_value(float value)
{
m_value = glm::vec2(value) * m_mask;
if (on_value_changed)
on_value_changed(this, glm::length(m_value));
}
float NodeSliderH::get_value()
{
return glm::length(m_value * m_mask);
}
void NodeSliderH::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
NodeBorder::parse_attributes(ka, attr);
switch (ka)
{
case kAttribute::Value:
m_value = glm::vec2(attr->FloatValue());
break;
default:
break;
}
}
kEventResult NodeSliderH::handle_event(Event* e)
{
NodeBorder::handle_event(e);
switch (e->m_type)
{
case kEventType::MouseDownL:
dragging = true;
mouse_capture();
{
m_old_value = m_value;
auto sz = GetSize();
auto pos = glm::clamp(((MouseEvent*)e)->m_pos - m_pos, { 0, 0 }, sz) * m_mask;
m_value = pos / glm::max({ 1, 1 }, sz);
if (on_value_changed)
on_value_changed(this, glm::length(m_value));
}
break;
case kEventType::MouseUpL:
mouse_release();
dragging = false;
break;
case kEventType::MouseMove:
if (dragging)
{
auto sz = GetSize();
auto pos = glm::clamp(((MouseEvent*)e)->m_pos - m_pos, { 0, 0 }, sz) * m_mask;
m_value = pos / glm::max({ 1, 1 }, sz);
if (on_value_changed)
on_value_changed(this, glm::length(m_value * m_mask));
}
break;
case kEventType::MouseCancel:
mouse_release();
if (dragging)
{
m_value = m_old_value;
set_value(glm::length(m_value));
if (on_value_changed)
on_value_changed(this, glm::length(m_value * m_mask));
}
dragging = false;
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}
Node* NodeSliderHue::clone_instantiate() const
{
return new NodeSliderHue();
}
void NodeSliderHue::clone_finalize(Node* dest) const
{
NodeSliderV::clone_finalize(dest);
NodeSliderHue* n = static_cast<NodeSliderHue*>(dest);
n->init_controls();
}
void NodeSliderHue::init()
{
NodeSliderV::init();
init_controls();
}
void NodeSliderHue::init_controls()
{
on_value_changed = [this](Node*, float value) {
m_color = glm::vec4(convert_hsv2rgb({ value, 1, 1 }), 1);
if (on_hue_changed)
on_hue_changed(this, m_color);
};
}
glm::vec4 NodeSliderHue::get_hue()
{
m_color = glm::vec4(convert_hsv2rgb({ glm::length(m_value * m_mask), 1, 1 }), 1);
return m_color;
}
void NodeSliderHue::draw()
{
using namespace ui;
ui::ShaderManager::use(kShader::ColorHue);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
//ui::ShaderManager::u_vec4(kShaderUniform::Col, m_color);
ui::ShaderManager::u_int(kShaderUniform::Direction, 1); // set vertical
m_plane.draw_fill();
NodeBorder::m_color = glm::vec4(0);
NodeSliderH::draw();
}

40
src/node_slider.h Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include "node_border.h"
class NodeSliderH : public NodeBorder
{
bool dragging = false;
public:
glm::vec2 m_mask{ 1, 0 };
glm::vec2 m_value{0};
glm::vec2 m_old_value;
std::function<void(Node* target, float value)> on_value_changed;
virtual Node* clone_instantiate() const override;
virtual void clone_copy(Node* dest) const override;
virtual void init() override;
virtual void draw() override;
void set_value(float value);
float get_value();
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override;
virtual kEventResult handle_event(Event* e) override;
};
class NodeSliderV : public NodeSliderH
{
public:
virtual Node* clone_instantiate() const override { return new NodeSliderV(); }
NodeSliderV() { m_mask = { 0, 1 }; }
};
class NodeSliderHue : public NodeSliderV
{
public:
glm::vec4 m_color;
std::function<void(Node* target, glm::vec4 color)> on_hue_changed;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
void init_controls();
glm::vec4 get_hue();
virtual void draw() override;
};

154
src/node_stroke_preview.cpp Normal file
View File

@@ -0,0 +1,154 @@
#include "pch.h"
#include "log.h"
#include "node_stroke_preview.h"
#include "texture.h"
#include "shader.h"
#include "bezier.h"
#include "canvas.h"
Node* NodeStrokePreview::clone_instantiate() const
{
return new NodeStrokePreview();
}
void NodeStrokePreview::clone_copy(Node* dest) const
{
NodeBorder::clone_copy(dest);
}
void NodeStrokePreview::clone_children(Node* dest) const
{
// stop children cloning
}
void NodeStrokePreview::clone_finalize(Node* dest) const
{
NodeStrokePreview* n = (NodeStrokePreview*)dest;
n->init_controls();
}
void NodeStrokePreview::init_controls()
{
m_mesh.create();
m_sampler.create(GL_LINEAR, GL_REPEAT);
m_sampler_brush.create();
m_sampler_brush.set_filter(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);
// TextureManager::load("data/thumbs/Round-Hard.png");
// ui::Canvas::I->m_current_brush.m_tex_id = const_hash("data/thumbs/Round-Hard.png");
}
void NodeStrokePreview::restore_context()
{
NodeBorder::restore_context();
init_controls();
if (m_size.x > 0 && m_size.y > 0)
m_rtt.create(m_size.x, m_size.y);
draw_stroke();
}
void NodeStrokePreview::clear_context()
{
NodeBorder::clear_context();
m_rtt.destroy();
}
void NodeStrokePreview::draw_stroke()
{
m_rtt.bindFramebuffer();
{
using namespace ui;
GLint vp[4];
GLfloat cc[4];
glGetIntegerv(GL_VIEWPORT, vp);
glGetFloatv(GL_COLOR_CLEAR_VALUE, cc);
glClearColor(1, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight());
glEnable(GL_BLEND);
glm::mat4 proj = glm::ortho<float>(0, (float)m_rtt.getWidth(), 0, (float)m_rtt.getHeight(), -1, 1);
auto b = m_brush;
m_stroke.reset();
m_stroke.start(b);
if (!m_stroke.m_keypoints.empty())
m_stroke.m_prev_sample.origin = m_stroke.m_keypoints[0].pos;
auto samples = m_stroke.compute_samples();
auto& tex = TextureManager::get(b.m_tex_id);
glActiveTexture(GL_TEXTURE0);
tex.bind();
m_sampler_brush.bind(0);
auto& stencil = TextureManager::get(const_hash("data/paper.jpg"));
glActiveTexture(GL_TEXTURE1);
stencil.bind();
m_sampler.bind(1);
if (true)
{
ShaderManager::use(kShader::BrushStroke);
ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 });
ShaderManager::u_int(kShaderUniform::Tex, 0);
ShaderManager::u_int(kShaderUniform::TexStencil, 1); // stencil
ShaderManager::u_vec2(kShaderUniform::Resolution, { m_rtt.getWidth(), m_rtt.getHeight() });
ShaderManager::u_vec2(kShaderUniform::StencilOffset, glm::vec2(0));
ShaderManager::u_float(kShaderUniform::StencilAlpha, b.m_tip_stencil);
m_mesh.draw(samples, proj);
}
//else
//{
// ShaderManager::use("stroke");
// ShaderManager::u_vec4(kShaderUniform::Col, m_brush.m_tip_color);
// ShaderManager::u_int(kShaderUniform::Tex, 0);
// for (const auto& s : samples)
// {
// auto mvp = proj *
// glm::translate(glm::vec3(s.pos, 0)) *
// glm::scale(glm::vec3(s.size, s.size, 1)) *
// glm::eulerAngleZ(s.angle);
// ShaderManager::u_mat4(kShaderUniform::MVP, mvp);
// ShaderManager::u_float(kShaderUniform::Alpha, s.flow);
// m_plane.draw_fill();
// }
//}
m_sampler_brush.unbind();
m_sampler.unbind();
tex.unbind();
glDisable(GL_BLEND);
glViewport(vp[0], vp[1], vp[2], vp[3]);
glClearColor(cc[0], cc[1], cc[2], cc[3]);
}
m_rtt.unbindFramebuffer();
}
void NodeStrokePreview::draw()
{
using namespace ui;
ui::ShaderManager::use(kShader::Texture);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
m_rtt.bindTexture();
m_sampler.bind(0);
m_plane.draw_fill();
m_sampler.unbind();
m_rtt.unbindTexture();
}
void NodeStrokePreview::handle_resize(glm::vec2 old_size, glm::vec2 new_size)
{
float pad = 30.f;
new_size *= root()->m_zoom;
float w = new_size.x;
float h = new_size.y;
std::vector<glm::vec2> kp = { { pad, pad },{ pad, h - pad },{ w - pad, pad },{ w - pad, h - pad } };
m_stroke.reset();
m_stroke.start(m_brush);
for (int i = 0; i < 20; i++)
m_stroke.add_point(BezierCurve::Bezier2D(kp, i / 20.f), 1.f);
m_rtt.destroy();
m_rtt.create((int)new_size.x, (int)new_size.y);
draw_stroke();
}

27
src/node_stroke_preview.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include "node_border.h"
#include "rtt.h"
#include "brush.h"
#include "texture.h"
class NodeStrokePreview : public NodeBorder
{
RTT m_rtt;
Sampler m_sampler;
Sampler m_sampler_brush;
ui::BrushMesh m_mesh;
public:
ui::Brush m_brush;
ui::Stroke m_stroke;
std::vector<glm::vec2> m_bez_points;
virtual Node* clone_instantiate() const override;
virtual void clone_copy(Node* dest) const override;
virtual void clone_children(Node* dest) const override;
virtual void clone_finalize(Node* dest) const override;
void init_controls();
virtual void restore_context() override;
virtual void clear_context() override;
void draw_stroke();
virtual void draw() override;
virtual void handle_resize(glm::vec2 old_size, glm::vec2 new_size) override;
};

99
src/node_text.cpp Normal file
View File

@@ -0,0 +1,99 @@
#include "pch.h"
#include "log.h"
#include "node_text.h"
#include "shader.h"
Node* NodeText::clone_instantiate() const
{
return new NodeText();
}
void NodeText::clone_copy(Node* dest) const
{
Node::clone_copy(dest);
NodeText* n = static_cast<NodeText*>(dest);
n->m_text_mesh.create();
n->m_text_mesh.update(font_id, m_text.c_str());
n->m_text = m_text;
n->m_font = m_font;
n->m_color = m_color;
n->m_font_size = m_font_size;
n->font_id = font_id;
}
void NodeText::create()
{
if (!m_font.empty())
{
char font[64];
sprintf(font, "%s-%d", m_font.c_str(), m_font_size);
font_id = (kFont)const_hash(font);
m_text_mesh.create();
m_text_mesh.update(font_id, m_text.c_str());
SetSize(m_text_mesh.bb);
}
}
void NodeText::set_font(kFont fontID)
{
font_id = fontID;
m_text_mesh.create();
m_text_mesh.update(font_id, m_text.c_str());
SetSize(m_text_mesh.bb);
}
void NodeText::restore_context()
{
Node::restore_context();
create();
}
void NodeText::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
Node::parse_attributes(ka, attr);
switch (ka)
{
case kAttribute::Text:
m_text = attr->Value();
break;
case kAttribute::FontFace:
m_font = attr->Value();
break;
case kAttribute::FontSize:
m_font_size = attr->IntValue();
break;
case kAttribute::Color:
{
glm::vec4 pad;
int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
if (n == 1)
m_color = glm::vec4(pad.x);
else
m_color = pad;
break;
}
default:
break;
}
}
void NodeText::set_text(const char* s)
{
m_text = s;
m_text_mesh.update(font_id, s);
SetSize(m_text_mesh.bb);
}
void NodeText::draw()
{
using namespace ui;
glm::mat4 pos = glm::translate(glm::vec3(glm::floor(m_pos), 0));
m_mvp = m_proj * pos;
ui::ShaderManager::use(kShader::Font);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
ui::ShaderManager::u_vec4(kShaderUniform::Col, m_color);
glEnable(GL_BLEND);
m_text_mesh.draw();
glDisable(GL_BLEND);
}

22
src/node_text.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include "node.h"
#include "font.h"
class NodeText : public Node
{
public:
TextMesh m_text_mesh;
std::string m_text;
std::string m_font;
glm::vec4 m_color{ 1, 1, 1, 1 };
int m_font_size;
kFont font_id;
virtual Node* clone_instantiate() const override;
virtual void clone_copy(Node* dest) const override;
virtual void create() override;
virtual void restore_context() override;
virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override;
void set_text(const char* s);
void set_font(kFont fontID);
virtual void draw() override;
};

102
src/node_text_input.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "pch.h"
#include "app.h"
#include "log.h"
#include "node_text_input.h"
Node* NodeTextInput::clone_instantiate() const
{
return new NodeTextInput();
}
void NodeTextInput::clone_finalize(Node* dest) const
{
NodeBorder::clone_copy(dest);
NodeTextInput* n = static_cast<NodeTextInput*>(dest);
if (n->m_children.size())
{
auto t = n->m_children[0];
n->m_text = (NodeText*)t.get();
}
n->m_string = m_string;
}
void NodeTextInput::init()
{
init_controls();
}
void NodeTextInput::init_controls()
{
m_text = new NodeText;
add_child(m_text);
m_text->m_font = "arial";
m_text->m_font_size = 11;
m_text->m_text = "TextInput";
m_text->create();
m_string = "TextInput";
}
kEventResult NodeTextInput::handle_event(Event* e)
{
KeyEvent* ke = (KeyEvent*)e;
switch (e->m_type)
{
case kEventType::MouseDownL:
App::I.showKeyboard();
break;
case kEventType::MouseUpL:
key_capture();
break;
case kEventType::KeyDown:
//switch (ke->m_key)
//{
//case VK_BACK:
// m_string.erase(m_string.end() - 1);
// m_text->set_text(m_string.c_str());
// break;
//default:
// break;
//}
break;
case kEventType::KeyChar:
if (ke->m_char == '\b') // backspace
{
if (!m_string.empty())
{
m_string.erase(m_string.end() - 1);
m_text->set_text(m_string.c_str());
}
}
else if (ke->m_char == 0x7f) // DEL
{
if (!m_string.empty())
{
m_string.erase(m_string.end() - 1);
m_text->set_text(m_string.c_str());
}
}
else if (ke->m_char == '\n' || ke->m_char == '\r') // enter/return
{
if (on_return)
on_return(this);
}
else if (ke->m_char >= 32 && ke->m_char < (32 + 96))
{
m_string += (char)ke->m_char;
m_text->set_text(m_string.c_str());
}
break;
default:
return kEventResult::Available;
break;
}
return kEventResult::Consumed;
}
void NodeTextInput::set_text(const std::string& s)
{
if (m_text)
m_text->set_text(s.c_str());
m_string = s;
}

17
src/node_text_input.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include "node_border.h"
#include "node_text.h"
class NodeTextInput : public NodeBorder
{
public:
NodeText* m_text;
std::string m_string;
std::function<void(NodeTextInput*target)> on_return;
virtual Node* clone_instantiate() const override;
virtual void clone_finalize(Node* dest) const override;
virtual void init() override;
virtual kEventResult handle_event(Event* e) override;
void init_controls();
void set_text(const std::string& s);
};

73
src/node_viewport.cpp Normal file
View File

@@ -0,0 +1,73 @@
#include "pch.h"
#include "log.h"
#include "node_viewport.h"
#include "shader.h"
void NodeViewport::draw()
{
using namespace ui;
glm::mat4 cam = glm::lookAt(glm::vec3(sinf(angle) * 10, 0, -10), glm::vec3(0, 0, 0), glm::vec3(0, -1, 0));
glm::mat4 proj = glm::perspective<float>(glm::radians(45.f), m_clip.z / m_clip.w, .1f, 100);
GLint vp[4];
GLfloat cc[4];
glGetIntegerv(GL_VIEWPORT, vp);
glGetFloatv(GL_COLOR_CLEAR_VALUE, cc);
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
auto box = m_clip * root()->m_zoom;
glm::ivec4 c = (glm::ivec4)glm::vec4(box.x, (int)(vp[3] - box.y - box.w), box.z, box.w);
glViewport(c.x, c.y, c.z, c.w);
TextureManager::get(m_tex_id).bind();
m_sampler->bind(0);
glEnable(GL_BLEND);
ui::ShaderManager::use(kShader::Texture);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, proj * cam);
m_faces->draw_fill();
m_sampler->unbind();
TextureManager::get(m_tex_id).unbind();
glDisable(GL_BLEND);
glViewport(vp[0], vp[1], vp[2], vp[3]);
glClearColor(cc[0], cc[1], cc[2], cc[3]);
}
Node* NodeViewport::clone_instantiate() const
{
return new NodeViewport;
}
void NodeViewport::create()
{
m_faces = std::make_unique<ui::Plane>();
m_faces->create<1>(10, 10);
m_sampler = std::make_unique<Sampler>();
m_sampler->create();
TextureManager::load("data/uvs.jpg");
m_tex_id = const_hash("data/uvs.jpg");
}
kEventResult NodeViewport::handle_event(Event* e)
{
Node::handle_event(e);
switch (e->m_type)
{
case kEventType::MouseDownL:
dragging = true;
drag_end = drag_start = ((MouseEvent*)e)->m_pos;
angle_old = angle;
break;
case kEventType::MouseUpL:
dragging = false;
break;
case kEventType::MouseMove:
if (dragging)
{
drag_end = ((MouseEvent*)e)->m_pos;
angle = angle_old + (drag_end - drag_start).x * .01f;
}
break;
default:
break;
}
return kEventResult::Consumed;
}

23
src/node_viewport.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include "node.h"
#include "shape.h"
#include "texture.h"
#include "event.h"
class NodeViewport : public Node
{
public:
std::unique_ptr<ui::Plane> m_faces;
std::unique_ptr<Sampler> m_sampler;
uint16_t m_tex_id;
glm::vec2 drag_start;
glm::vec2 drag_end;
bool dragging = false;
float angle = 0.0f;
float angle_old;
virtual void draw() override;
virtual Node* clone_instantiate() const override;
virtual void create() override;
virtual kEventResult handle_event(Event* e) override;
};

36
src/objc_utils.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include "pch.h"
#include "objc_utils.h"
#ifdef __OBJC__
@implementation PathWithModDate
@end
@implementation ObjcUtils
+ (NSArray*)getFilesAtPathSortedByModificationDate:(NSString*)folderPath
{
NSArray *allPaths = [NSFileManager.defaultManager contentsOfDirectoryAtPath:folderPath error:nil];
NSMutableArray *sortedPaths = [NSMutableArray new];
for (NSString *path in allPaths) {
NSString *fullPath = [folderPath stringByAppendingPathComponent:path];
NSDictionary *attr = [NSFileManager.defaultManager attributesOfItemAtPath:fullPath error:nil];
NSDate *modDate = [attr objectForKey:NSFileModificationDate];
PathWithModDate *pathWithDate = [[PathWithModDate alloc] init];
pathWithDate.path = fullPath;
pathWithDate.modDate = modDate;
[sortedPaths addObject:pathWithDate];
}
[sortedPaths sortUsingComparator:^(PathWithModDate *path1, PathWithModDate *path2) {
// Descending (most recently modified first)
return [path2.modDate compare:path1.modDate];
}];
return sortedPaths;
}
@end
#endif

15
src/objc_utils.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#ifdef __OBJC__
@interface PathWithModDate : NSObject
@property (strong) NSString *path;
@property (strong) NSDate *modDate;
@end
@interface ObjcUtils : NSObject
@end
#endif

19
src/pch.cpp Normal file
View File

@@ -0,0 +1,19 @@
#include "pch.h"
#define STB_TRUETYPE_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb/stb_truetype.h>
#include <stb/stb_image.h>
#include <stb/stb_image_write.h>
#ifdef DEBUG
#pragma comment (lib, "libcurl_debug.lib")
#else
#pragma comment (lib, "libcurl.lib")
#endif // DEBUG
#ifdef _WIN32
#pragma comment(lib, "BugTrapU-x64.lib")
#pragma comment(lib, "shell32.lib")
#endif // _WIN32

132
src/pch.h Normal file
View File

@@ -0,0 +1,132 @@
//#pragma once
#include "version.h"
#define USE_VBO 1
#define USE_SAMPLER 1
#ifdef __APPLE__
#include "TargetConditionals.h"
#if TARGET_OS_IPHONE && TARGET_IPHONE_SIMULATOR
#define TARGET_OS_IOS 1
#define __IOS__ 1
#include <CoreFoundation/CoreFoundation.h>
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
#import <ZipArchive/ZipArchive.h>
#endif
#include <OpenGLES/ES3/gl.h>
#include <OpenGLES/ES3/glext.h>
#define SHADER_VERSION "#version 300 es\n"
#elif TARGET_OS_IPHONE
#define TARGET_OS_IOS 1
#define __IOS__ 1
#include <CoreFoundation/CoreFoundation.h>
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
#import <ZipArchive/ZipArchive.h>
#endif
#include <OpenGLES/ES3/gl.h>
#include <OpenGLES/ES3/glext.h>
#define SHADER_VERSION "#version 300 es\n"
#else
#define TARGET_OS_OSX 1
#define __OSX__ 1
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <ZipArchive/ZipArchive.h>
#endif
#include <OpenGL/gl3.h>
#include <OpenGL/gl3ext.h>
#define SHADER_VERSION "#version 150\n"
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#elif __ANDROID__
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <sys/stat.h>
#include <android/sensor.h>
#include <android/log.h>
#include <android_native_app_glue.h>
#define SHADER_VERSION "#version 300 es\n"
#elif _WIN32
#define _USE_MATH_DEFINES
#define _CRT_SECURE_NO_WARNINGS
#define _SCL_SECURE_NO_WARNINGS
#include <windows.h>
#include <windowsx.h>
#include <tchar.h>
#include <gl\glew.h>
#include <gl\wglew.h>
#include <gl\GL.h>
#include <BugTrap.h>
#include <shlobj.h>
#define SHADER_VERSION "#version 150\n"
#endif
#define NS_START namespace ui {
#define NS_END }
#define SIXPLETTE(I) {I, I, I, I, I, I}
#ifdef __cplusplus
#include <map>
#include <set>
#include <array>
#include <cmath>
#include <stack>
#include <regex>
#include <mutex>
#include <queue>
#include <memory>
#include <string>
#include <vector>
#include <random>
#include <thread>
#include <future>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <functional>
#include <condition_variable>
#define GLM_FORCE_RADIANS
//#define GLM_FORCE_SWIZZLE
//#define GLM_FORCE_MESSAGES
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include <glm/gtx/euler_angles.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <glm/gtx/intersect.hpp>
#include <tinyxml2.h>
#include <jpge.h>
#include <jpgd.h>
#include <base64.h>
#endif
#include <yoga/Yoga.h>
#include <stb/stb_truetype.h>
#include <stb/stb_image.h>
#include <stb/stb_image_write.h>
#include <curl/curl.h>

Some files were not shown because too many files have changed in this diff Show More