1050 lines
33 KiB
C++
1050 lines
33 KiB
C++
#include "pch.h"
|
|
#include "log.h"
|
|
#include "node_panel_brush.h"
|
|
#include "asset.h"
|
|
#include "texture.h"
|
|
|
|
#ifdef __APPLE__
|
|
#include <Foundation/Foundation.h>
|
|
#endif
|
|
#include "canvas.h"
|
|
#include "app.h"
|
|
#include "abr.h"
|
|
|
|
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();
|
|
}
|
|
|
|
bool NodeButtonBrush::read(BinaryStreamReader& r)
|
|
{
|
|
Serializer::Descriptor d;
|
|
r >> d;
|
|
d.value<Serializer::CString>("brush_name", brush_name);
|
|
d.value<Serializer::CString>("high_path", high_path);
|
|
d.value<Serializer::CString>("thumb_path", thumb_path);
|
|
d.value<Serializer::Boolean>("m_user_brush", m_user_brush);
|
|
high_path = str_replace(high_path, "{data_path}", App::I->data_path);
|
|
thumb_path = str_replace(thumb_path, "{data_path}", App::I->data_path);
|
|
return true;
|
|
}
|
|
|
|
void NodeButtonBrush::write(BinaryStreamWriter& w) const
|
|
{
|
|
Serializer::Descriptor d;
|
|
d.class_id = "brush";
|
|
d.name = L"Brush class";
|
|
d.props["brush_name"] = std::make_shared<Serializer::CString>(brush_name);
|
|
d.props["high_path"] = std::make_shared<Serializer::CString>(
|
|
str_replace(high_path, App::I->data_path, "{data_path}"));
|
|
d.props["thumb_path"] = std::make_shared<Serializer::CString>(
|
|
str_replace(thumb_path, App::I->data_path, "{data_path}"));
|
|
d.props["m_user_brush"] = std::make_shared<Serializer::Boolean>(m_user_brush);
|
|
w << d;
|
|
}
|
|
|
|
Node* NodePanelBrush::clone_instantiate() const
|
|
{
|
|
return new NodePanelBrush();
|
|
}
|
|
|
|
void NodePanelBrush::init()
|
|
{
|
|
init_template("tpl-panel-brushes");
|
|
|
|
m_btn_add = find<NodeButtonCustom>("btn-add");
|
|
m_btn_add->on_click = [this](Node*) {
|
|
App::I->pick_file({ "JPG", "PNG" }, [this](std::string path) {
|
|
std::string name, base, ext;
|
|
std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)");
|
|
std::smatch m;
|
|
if (!std::regex_search(path, m, r))
|
|
return;
|
|
base = m[1].str();
|
|
name = m[2].str();
|
|
ext = m[3].str();
|
|
Image img;
|
|
if (!m_dir_name.empty() && img.load_file(path))
|
|
{
|
|
std::string path_high = App::I->data_path + "/" + m_dir_name + "/" + name + ".png";
|
|
std::string path_thumb = App::I->data_path + "/" + m_dir_name + "/thumbs/" + name + ".png";
|
|
|
|
//img = img.resize_squared(glm::u8vec4(255));
|
|
if (m_dir_name == "brushes")
|
|
img.gayscale_alpha();
|
|
|
|
auto thumb = img.resize(64, 64).resize_squared(glm::u8vec4(255));
|
|
thumb.save_png(path_thumb);
|
|
//auto po2 = img.resize_power2();
|
|
img.save_png(path_high);
|
|
|
|
NodeButtonBrush* brush = new NodeButtonBrush;
|
|
m_container->add_child(brush);
|
|
brush->init();
|
|
brush->create();
|
|
brush->loaded();
|
|
brush->set_icon(path_thumb.c_str());
|
|
brush->thumb_path = path_thumb;
|
|
brush->high_path = path_high;
|
|
brush->brush_name = name;
|
|
brush->m_user_brush = true;
|
|
brush->on_click = std::bind(&NodePanelBrush::handle_click, this, std::placeholders::_1);
|
|
save();
|
|
}
|
|
});
|
|
};
|
|
|
|
m_btn_remove = find<NodeButtonCustom>("btn-remove");
|
|
m_btn_remove->on_click = [this](Node*) {
|
|
if (m_current)
|
|
{
|
|
int idx = m_container->get_child_index(m_current);
|
|
if (m_current->m_user_brush)
|
|
{
|
|
// only delete user brushes
|
|
Asset::delete_file(m_current->thumb_path);
|
|
Asset::delete_file(m_current->high_path);
|
|
}
|
|
m_container->remove_child(m_current);
|
|
if (m_container->m_children.size() > 0)
|
|
{
|
|
idx = std::max(0, std::min(idx, (int)m_container->m_children.size() - 1));
|
|
m_current = (NodeButtonBrush*)m_container->m_children[idx].get();
|
|
m_current->m_selected = true;
|
|
if (on_brush_changed)
|
|
on_brush_changed(this, idx);
|
|
}
|
|
else
|
|
{
|
|
m_current = nullptr;
|
|
}
|
|
save();
|
|
}
|
|
};
|
|
|
|
m_btn_up = find<NodeButtonCustom>("btn-up");
|
|
m_btn_up->on_click = [this](Node*) {
|
|
if (m_current)
|
|
{
|
|
int idx = m_container->get_child_index(m_current);
|
|
idx = std::max(0, std::min(idx - 1, (int)m_container->m_children.size() - 1));
|
|
m_container->move_child(m_current, idx);
|
|
save();
|
|
}
|
|
};
|
|
|
|
m_btn_down = find<NodeButtonCustom>("btn-down");
|
|
m_btn_down->on_click = [this](Node*) {
|
|
if (m_current)
|
|
{
|
|
int idx = m_container->get_child_index(m_current);
|
|
idx = std::max(0, std::min(idx + 1, (int)m_container->m_children.size() - 1));
|
|
m_container->move_child(m_current, idx);
|
|
save();
|
|
}
|
|
};
|
|
|
|
m_container = find<NodeScroll>("brushes");
|
|
|
|
if (Asset::exist(App::I->data_path + "/settings/" + m_dir_name + ".bin") && !restore())
|
|
{
|
|
auto mb = App::I->message_box("Brushes", "Could not read brush textures file, it will be deleted.", true);
|
|
mb->btn_ok->on_click = [this, mb](Node*) {
|
|
Asset::delete_file(App::I->data_path + "/settings/" + m_dir_name + ".bin");
|
|
mb->destroy();
|
|
};
|
|
mb->btn_cancel->on_click = [mb](Node*) {
|
|
mb->destroy();
|
|
};
|
|
}
|
|
|
|
if (m_container->m_children.empty() && !m_dir_name.empty())
|
|
{
|
|
auto icons = Asset::list_files("data/" + m_dir_name, ".*\\.png$");
|
|
for (auto& i : icons)
|
|
{
|
|
std::string path = "data/" + m_dir_name + "/thumbs/" + i;
|
|
std::string path_hi = "data/" + m_dir_name + "/" + i;
|
|
NodeButtonBrush* brush = new NodeButtonBrush;
|
|
m_container->add_child(brush);
|
|
brush->init();
|
|
brush->create();
|
|
brush->loaded();
|
|
brush->set_icon(path.c_str());
|
|
brush->thumb_path = path;
|
|
brush->high_path = path_hi;
|
|
brush->brush_name = i;
|
|
brush->m_user_brush = false; // system brush, cannot be deleted from file
|
|
brush->on_click = std::bind(&NodePanelBrush::handle_click, this, std::placeholders::_1);
|
|
}
|
|
|
|
auto custom_icons = Asset::list_files(App::I->data_path + "/" + m_dir_name, ".*\\.png$");
|
|
for (auto& i : custom_icons)
|
|
{
|
|
std::string path_thumb = App::I->data_path + "/" + m_dir_name + "/thumbs/" + i;
|
|
std::string path_high = App::I->data_path + "/" + m_dir_name + "/" + i;
|
|
NodeButtonBrush* brush = new NodeButtonBrush;
|
|
m_container->add_child(brush);
|
|
brush->init();
|
|
brush->create();
|
|
brush->loaded();
|
|
brush->set_icon(path_thumb.c_str());
|
|
brush->thumb_path = path_thumb;
|
|
brush->high_path = path_high;
|
|
brush->brush_name = i;
|
|
brush->m_user_brush = true;
|
|
brush->on_click = std::bind(&NodePanelBrush::handle_click, this, std::placeholders::_1);
|
|
}
|
|
}
|
|
save();
|
|
}
|
|
|
|
kEventResult NodePanelBrush::handle_event(Event* e)
|
|
{
|
|
switch (e->m_type)
|
|
{
|
|
case kEventType::MouseLeave:
|
|
if (!m_interacted)
|
|
break;
|
|
// else fall through
|
|
case kEventType::MouseUpL:
|
|
if (!m_mouse_inside)
|
|
{
|
|
mouse_release();
|
|
m_parent->remove_child(this);
|
|
if (on_popup_close)
|
|
on_popup_close(this);
|
|
}
|
|
break;
|
|
default:
|
|
return kEventResult::Available;
|
|
break;
|
|
}
|
|
return kEventResult::Consumed;
|
|
}
|
|
|
|
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_container->get_child_index(target));
|
|
m_interacted = true;
|
|
}
|
|
|
|
int NodePanelBrush::find_brush(const std::string & name) const
|
|
{
|
|
for (int i = 0; i < m_container->m_children.size(); i++)
|
|
{
|
|
NodeButtonBrush* b = (NodeButtonBrush*)m_container->m_children[i].get();
|
|
if (b->brush_name.find(name) != std::string::npos)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
std::string NodePanelBrush::get_texture_path(int index) const
|
|
{
|
|
if (index < 0 || index >= m_container->m_children.size())
|
|
return "";
|
|
return ((NodeButtonBrush*)m_container->m_children[index].get())->high_path;
|
|
}
|
|
|
|
std::string NodePanelBrush::get_thumb_path(int index) const
|
|
{
|
|
if (index < 0 || index >= m_container->m_children.size())
|
|
return "";
|
|
return ((NodeButtonBrush*)m_container->m_children[index].get())->thumb_path;
|
|
}
|
|
|
|
bool NodePanelBrush::save()
|
|
{
|
|
std::ofstream f(App::I->data_path + "/settings/" + m_dir_name + ".bin", std::ios::binary);
|
|
if (f.good())
|
|
{
|
|
BinaryStreamWriter sw;
|
|
sw.init();
|
|
sw.wstring_raw("PPVR"); // magic code
|
|
sw.wu16(0); // version major
|
|
sw.wu16(1); // minor
|
|
sw.wu32((int)m_container->m_children.size()); // number of items
|
|
for (const auto& child : m_container->m_children)
|
|
{
|
|
auto b = std::static_pointer_cast<NodeButtonBrush>(child);
|
|
sw << *b;
|
|
}
|
|
f.write((char*)sw.m_data.data(), sw.m_data.size());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool NodePanelBrush::restore()
|
|
{
|
|
Asset f;
|
|
auto path = App::I->data_path + "/settings/" + m_dir_name + ".bin";
|
|
if (f.open(path.c_str()))
|
|
{
|
|
f.read_all();
|
|
|
|
BinaryStreamReader sr;
|
|
sr.init(f.m_data, f.m_len);
|
|
|
|
// sanity checks
|
|
if (sr.rstring(4) != "PPVR")
|
|
{
|
|
LOG("PPVR tag not found")
|
|
return false;
|
|
}
|
|
auto vmaj = sr.ru16();
|
|
auto vmin = sr.ru16();
|
|
if (vmaj != 0 && vmin != 1)
|
|
{
|
|
LOG("unrecognised version %d.%d", vmaj, vmin);
|
|
return false;
|
|
}
|
|
|
|
auto count = sr.ru32();
|
|
|
|
for (int k = 0; k < count; k++)
|
|
{
|
|
auto b = std::make_shared<NodeButtonBrush>();
|
|
if (!b->read(sr))
|
|
{
|
|
LOG("error deserializing the button brush");
|
|
return false;
|
|
}
|
|
|
|
if (Asset::exist(b->high_path))
|
|
{
|
|
m_container->add_child(b);
|
|
b->init();
|
|
b->create();
|
|
b->loaded();
|
|
b->set_icon(b->thumb_path.c_str());
|
|
b->on_click = std::bind(&NodePanelBrush::handle_click, this, std::placeholders::_1);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void NodePanelBrush::clear()
|
|
{
|
|
m_container->remove_all_children();
|
|
}
|
|
|
|
void NodePanelBrush::added(Node* parent)
|
|
{
|
|
m_interacted = false;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
std::vector<NodePanelBrushPreset*> NodePanelBrushPreset::s_panels;
|
|
|
|
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 = find<NodeImage>("thumb");
|
|
m_caption_size = find<NodeText>("caption-size");
|
|
m_preview = find<NodeStrokePreview>("canvas");
|
|
m_preview->m_min_flow = 1.f;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
NodePanelBrushPreset::NodePanelBrushPreset()
|
|
{
|
|
s_panels.push_back(this);
|
|
}
|
|
|
|
NodePanelBrushPreset::~NodePanelBrushPreset()
|
|
{
|
|
s_panels.erase(std::remove(s_panels.begin(), s_panels.end(), this));
|
|
}
|
|
|
|
//---
|
|
|
|
Node* NodePanelBrushPreset::clone_instantiate() const
|
|
{
|
|
return new NodePanelBrushPreset();
|
|
}
|
|
|
|
void NodePanelBrushPreset::init()
|
|
{
|
|
init_template("tpl-panel-brush-preset");
|
|
m_container = find<Node>("brushes");
|
|
m_btn_add = find<NodeButtonCustom>("btn-add");
|
|
m_btn_add->on_click = [this] (Node*) {
|
|
for (auto p : s_panels)
|
|
p->add_brush(std::make_shared<Brush>(*Canvas::I->m_current_brush));
|
|
save();
|
|
};
|
|
m_btn_up = find<NodeButtonCustom>("btn-up");
|
|
m_btn_up->on_click = [this](Node*) {
|
|
if (m_current)
|
|
{
|
|
int idx = m_container->get_child_index(m_current);
|
|
int to = std::max(idx - 1, 0);
|
|
for (auto p : s_panels)
|
|
p->m_container->move_child(p->m_container->m_children[idx].get(), to);
|
|
save();
|
|
}
|
|
};
|
|
m_btn_down = find<NodeButtonCustom>("btn-down");
|
|
m_btn_down->on_click = [this](Node*) {
|
|
if (m_current)
|
|
{
|
|
int idx = m_container->get_child_index(m_current);
|
|
int to = std::min(idx + 1, (int)m_container->m_children.size() - 1);
|
|
for (auto p : s_panels)
|
|
p->m_container->move_child(p->m_container->m_children[idx].get(), to);
|
|
save();
|
|
}
|
|
};
|
|
/*
|
|
m_btn_save = find<NodeButtonCustom>("btn-save");
|
|
m_btn_save->on_click = [this](Node*) {
|
|
if (m_current)
|
|
{
|
|
*m_current->m_brush = *Canvas::I->m_current_brush;
|
|
m_current->m_preview->draw_stroke();
|
|
m_current->m_thumb->set_image(m_current->m_brush->m_brush_thumb_path);
|
|
save();
|
|
}
|
|
};
|
|
*/
|
|
m_btn_delete = find<NodeButtonCustom>("btn-remove");
|
|
m_btn_delete->on_click = [this](Node*) {
|
|
if (!m_current)
|
|
return;
|
|
int index = m_container->get_child_index(m_current);
|
|
for (auto p : s_panels)
|
|
{
|
|
bool new_current = !p->m_current || p->m_container->get_child_index(p->m_current) == index;
|
|
p->m_container->m_children[index]->destroy();
|
|
if (p->m_container->m_children.empty())
|
|
{
|
|
p->m_current = nullptr;
|
|
}
|
|
else if (new_current)
|
|
{
|
|
int next = std::min<int>((int)p->m_container->m_children.size() - 1, index);
|
|
p->m_current = (NodeBrushPresetItem*)p->m_container->m_children[next].get();
|
|
p->m_current->m_selected = true;
|
|
}
|
|
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
|
|
}
|
|
save();
|
|
};
|
|
m_btn_menu = find<NodeButtonCustom>("btn-menu");
|
|
m_btn_menu->on_click = [this](Node* b) {
|
|
auto popup = m_manager->instantiate<NodePopupMenu>("tpl-brush-popup");
|
|
popup->SetPosition(b->m_pos.x + b->m_size.x, b->m_pos.y);
|
|
root()->add_child(popup);
|
|
root()->update();
|
|
auto bounds = root()->GetSize() - zw(popup->get_children_rect());
|
|
popup->SetPosition(glm::clamp(popup->m_pos, { 0, 0 }, bounds));
|
|
popup->on_select = [this, popup] (Node* target, int index) {
|
|
switch (index)
|
|
{
|
|
case 0: // import file
|
|
App::I->pick_file({"abr", "ppbr"}, [this] (std::string path) {
|
|
std::thread([this, path] {
|
|
BT_SetTerminate();
|
|
import_brush(path);
|
|
for (auto p : s_panels)
|
|
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
|
|
}).detach();
|
|
});
|
|
break;
|
|
case 1: // export file
|
|
App::I->dialog_ppbr_export();
|
|
break;
|
|
case 2: // download
|
|
break;
|
|
case 3: // upload
|
|
break;
|
|
case 4: // clear presets
|
|
{
|
|
auto mb = App::I->message_box("Clear Presets", "Do you want to remove all the Brush Presets?", true);
|
|
mb->btn_ok->m_text->set_text("Yes");
|
|
mb->btn_cancel->m_text->set_text("No");
|
|
mb->btn_ok->on_click = mb->on_submit = [this, mb](Node*) {
|
|
App::I->presets->clear_brushes();
|
|
App::I->presets->save();
|
|
mb->destroy();
|
|
};
|
|
break;
|
|
}
|
|
}
|
|
popup->destroy();
|
|
};
|
|
};
|
|
m_btn_import = find<NodeButton>("import");
|
|
m_btn_import->on_click = [this] (Node*) {
|
|
App::I->pick_file({ "abr", "ppbr" }, [this](std::string path) {
|
|
std::thread([this, path] {
|
|
BT_SetTerminate();
|
|
import_brush(path);
|
|
for (auto p : s_panels)
|
|
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
|
|
}).detach();
|
|
});
|
|
};
|
|
m_btn_download = find<NodeButton>("download");
|
|
m_btn_download->on_click = [] (Node*) {
|
|
App::I->dialog_preset_download();
|
|
};
|
|
m_notification = find("notification");
|
|
|
|
if (Asset::exist(App::I->data_path + "/settings/presets.bin") && !restore())
|
|
{
|
|
auto mb = App::I->message_box("Presets", "Could not read brush presets file, it will be deleted.", true);
|
|
mb->btn_ok->on_click = [mb](Node*) {
|
|
Asset::delete_file(App::I->data_path + "/settings/presets.bin");
|
|
mb->destroy();
|
|
};
|
|
mb->btn_cancel->on_click = [mb](Node*) {
|
|
mb->destroy();
|
|
};
|
|
}
|
|
m_notification->SetVisibility(m_container->m_children.size() == 0);
|
|
}
|
|
|
|
kEventResult NodePanelBrushPreset::handle_event(Event* e)
|
|
{
|
|
switch (e->m_type)
|
|
{
|
|
case kEventType::MouseLeave:
|
|
if (!m_interacted)
|
|
break;
|
|
// else fall through
|
|
case kEventType::MouseUpL:
|
|
if (!m_mouse_inside)
|
|
{
|
|
mouse_release();
|
|
m_parent->remove_child(this);
|
|
if (on_popup_close)
|
|
on_popup_close(this);
|
|
}
|
|
break;
|
|
default:
|
|
return kEventResult::Available;
|
|
break;
|
|
}
|
|
return kEventResult::Consumed;
|
|
}
|
|
|
|
void NodePanelBrushPreset::handle_click(Node* target)
|
|
{
|
|
int idx = m_container->get_child_index(target);
|
|
for (auto p : s_panels)
|
|
{
|
|
if (p->m_current)
|
|
p->m_current->m_selected = false;
|
|
p->m_current = (NodeBrushPresetItem*)p->m_container->get_child_at(idx);
|
|
p->m_current->m_selected = true;
|
|
p->m_interacted = true;
|
|
}
|
|
if (on_brush_changed)
|
|
on_brush_changed(this, m_current->m_brush);
|
|
}
|
|
|
|
bool NodePanelBrushPreset::save()
|
|
{
|
|
auto path = App::I->data_path + "/settings/presets.bin";
|
|
std::ofstream f(path, std::ios::binary);
|
|
if (f.good())
|
|
{
|
|
BinaryStreamWriter sw;
|
|
sw.init();
|
|
sw.wstring_raw("PPVR");
|
|
sw.wu16(0);
|
|
sw.wu16(1);
|
|
sw.wu32((int)m_container->m_children.size());
|
|
for (int ci = 0; ci < m_container->m_children.size(); ci++)
|
|
{
|
|
auto bpi = static_cast<NodeBrushPresetItem*>(m_container->get_child_at(ci));
|
|
sw << *bpi->m_brush;
|
|
}
|
|
f.write((char*)sw.m_data.data(), sw.m_data.size());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool NodePanelBrushPreset::restore()
|
|
{
|
|
auto path = App::I->data_path + "/settings/presets.bin";
|
|
Asset f;
|
|
if (f.open(path.c_str()))
|
|
{
|
|
f.read_all();
|
|
|
|
BinaryStreamReader sr;
|
|
sr.init(f.m_data, f.m_len);
|
|
|
|
// sanity checks
|
|
auto magic = sr.rstring(4);
|
|
if (magic != "PPVR")
|
|
{
|
|
LOG("PPVR tag not found")
|
|
return false;
|
|
}
|
|
auto vmaj = sr.ru16();
|
|
auto vmin = sr.ru16();
|
|
if (vmaj != 0 && vmin != 1)
|
|
{
|
|
LOG("unrecognised version %d.%d", vmaj, vmin);
|
|
return false;
|
|
}
|
|
|
|
auto count = sr.ru32();
|
|
|
|
for (int k = 0; k < count; k++)
|
|
{
|
|
auto b = std::make_shared<Brush>();
|
|
if (!b->read(sr))
|
|
{
|
|
LOG("error deserializing the brush");
|
|
return false;
|
|
}
|
|
|
|
if (b->valid())
|
|
{
|
|
add_brush(b);
|
|
}
|
|
}
|
|
m_notification->SetVisibility(m_container->m_children.size() == 0);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void NodePanelBrushPreset::add_brush(std::shared_ptr<Brush> brush)
|
|
{
|
|
NodeBrushPresetItem* b = new NodeBrushPresetItem;
|
|
m_container->add_child(b);
|
|
b->init();
|
|
b->create();
|
|
b->loaded();
|
|
b->thumb_path = brush->m_brush_thumb_path;
|
|
b->high_path = brush->m_brush_path;
|
|
b->m_brush = brush;
|
|
b->m_preview->m_brush = brush;
|
|
b->m_preview->draw_stroke();
|
|
b->m_thumb->m_use_mipmaps = true;
|
|
b->m_thumb->set_image(brush->m_brush_thumb_path);
|
|
b->m_caption_size->set_text_format("%d", (int)brush->m_tip_size);
|
|
b->on_click = std::bind(&NodePanelBrushPreset::handle_click, this, std::placeholders::_1);
|
|
m_notification->SetVisibility(m_container->m_children.size() == 0);
|
|
}
|
|
|
|
bool NodePanelBrushPreset::export_ppbr(const std::string& path_in, const PPBRInfo& info_data)
|
|
{
|
|
std::string path = path_in;
|
|
if (path_in.find(".ppbr") == std::string::npos)
|
|
path += ".ppbr";
|
|
LOG("export ppbr to: %s", path.c_str());
|
|
|
|
std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)?$)");
|
|
std::smatch m;
|
|
if (!std::regex_search(path, m, r))
|
|
return false;
|
|
auto base = m[1].str();
|
|
auto name = m[2].str();
|
|
auto ext = m[3].str();
|
|
|
|
#if __OSX__
|
|
std::string out_path = info_data.dest_path + "/" + name + "_data";
|
|
#else
|
|
std::string out_path = base + "/" + name + "_data";
|
|
#endif
|
|
|
|
bool path_created = info_data.export_data && !out_path.empty() ? Asset::create_dir(out_path) : false;
|
|
|
|
std::ofstream f(path, std::ios::binary);
|
|
if (f.good())
|
|
{
|
|
BinaryStreamWriter sw;
|
|
sw.init(BinaryStream::ByteOrder::LittleEndian);
|
|
sw.wstring_raw("PPBR");
|
|
sw.wu16(0);
|
|
sw.wu16(1);
|
|
|
|
// count list of images
|
|
std::set<std::string> img_brushes;
|
|
std::set<std::string> img_patterns;
|
|
for (auto& c : m_container->m_children)
|
|
{
|
|
auto bpi = std::static_pointer_cast<NodeBrushPresetItem>(c);
|
|
if (!bpi->m_brush->m_brush_path.empty() && !Asset::is_asset(bpi->m_brush->m_brush_path))
|
|
img_brushes.insert(bpi->m_brush->m_brush_path);
|
|
if (!bpi->m_brush->m_dual_path.empty() && !Asset::is_asset(bpi->m_brush->m_dual_path))
|
|
img_brushes.insert(bpi->m_brush->m_dual_path);
|
|
if (!bpi->m_brush->m_pattern_path.empty() && !Asset::is_asset(bpi->m_brush->m_pattern_path))
|
|
img_patterns.insert(bpi->m_brush->m_pattern_path);
|
|
}
|
|
|
|
Serializer::Descriptor info;
|
|
info.class_id = "ppbr_info";
|
|
info.name = L"info header";
|
|
|
|
bool has_header_image = info_data.header_image != nullptr;
|
|
|
|
info.props["author"] = std::make_shared<Serializer::String>(info_data.author);
|
|
info.props["email"] = std::make_shared<Serializer::String>(info_data.email);
|
|
info.props["url"] = std::make_shared<Serializer::String>(info_data.url);
|
|
info.props["descr"] = std::make_shared<Serializer::String>(info_data.descr);
|
|
info.props["has_header_image"] = std::make_shared<Serializer::Boolean>(has_header_image);
|
|
info.props["num_brush_tips"] = std::make_shared<Serializer::Integer>(img_brushes.size());
|
|
info.props["num_brush_patt"] = std::make_shared<Serializer::Integer>(img_patterns.size());
|
|
info.props["num_brushes"] = std::make_shared<Serializer::Integer>(m_container->m_children.size());
|
|
|
|
auto pb = App::I->show_progress("Exporting PPBR", 1 + img_brushes.size() +
|
|
img_patterns.size() + m_container->m_children.size() * 2);
|
|
|
|
sw << info;
|
|
|
|
// header image
|
|
if (has_header_image)
|
|
{
|
|
sw << *info_data.header_image;
|
|
if (path_created)
|
|
info_data.header_image->save_jpg(out_path + "/header.jpg", 75);
|
|
}
|
|
|
|
pb->increment();
|
|
|
|
// create previews
|
|
sw.wu32((int)m_container->m_children.size());
|
|
auto pr = std::make_unique<NodeStrokePreview>();
|
|
pr->m_preview_size = pr->m_size = { 256, 128 };
|
|
int thumb_counter = 0;
|
|
for (auto& c : m_container->m_children)
|
|
{
|
|
auto bpi = std::static_pointer_cast<NodeBrushPresetItem>(c);
|
|
pr->m_brush = std::make_shared<Brush>(*bpi->m_brush); // create copy
|
|
pr->m_brush->load();
|
|
Image img = pr->render_to_image();
|
|
img.file_name = pr->m_brush->m_name;
|
|
sw << img;
|
|
if (path_created)
|
|
img.save_jpg(fmt::format(out_path + "/thumb-{:04d}.jpg", thumb_counter), 75);
|
|
thumb_counter++;
|
|
pb->increment();
|
|
}
|
|
|
|
// write brushes
|
|
sw.wu32((int)img_brushes.size());
|
|
for (std::string image_path : img_brushes)
|
|
{
|
|
Image img;
|
|
if (!img.load(image_path))
|
|
LOG("export_ppbr failed to load image: %s", image_path.c_str());
|
|
sw << img;
|
|
pb->increment();
|
|
}
|
|
|
|
// write patterns
|
|
sw.wu32((int)img_patterns.size());
|
|
for (std::string image_path : img_patterns)
|
|
{
|
|
Image img;
|
|
if (!img.load(image_path))
|
|
LOG("export_ppbr failed to load image: %s", image_path.c_str());
|
|
sw << img;
|
|
pb->increment();
|
|
}
|
|
|
|
// write brush settings
|
|
sw.wu32((int)m_container->m_children.size());
|
|
for (auto& c : m_container->m_children)
|
|
{
|
|
auto bpi = std::static_pointer_cast<NodeBrushPresetItem>(c);
|
|
sw << *bpi->m_brush;
|
|
pb->increment();
|
|
}
|
|
f.write((char*)sw.m_data.data(), sw.m_data.size());
|
|
|
|
pb->destroy();
|
|
|
|
return true;
|
|
}
|
|
LOG("export failed file creation");
|
|
return false;
|
|
}
|
|
|
|
bool NodePanelBrushPreset::import_ppbr(const std::string& path)
|
|
{
|
|
Asset f;
|
|
if (f.open(path.c_str()))
|
|
{
|
|
f.read_all();
|
|
|
|
BinaryStreamReader sr;
|
|
sr.init(f.m_data, f.m_len, BinaryStream::ByteOrder::LittleEndian);
|
|
|
|
// sanity checks
|
|
auto magic = sr.rstring(4);
|
|
if (magic != "PPBR")
|
|
{
|
|
LOG("PPBR tag not found")
|
|
return false;
|
|
}
|
|
auto vmaj = sr.ru16();
|
|
auto vmin = sr.ru16();
|
|
if (vmaj != 0 && vmin != 1)
|
|
{
|
|
LOG("unrecognised version %d.%d", vmaj, vmin);
|
|
return false;
|
|
}
|
|
|
|
Serializer::Descriptor info;
|
|
sr >> info;
|
|
|
|
int num_brush_tips = info.value<Serializer::Integer>("num_brush_tips");
|
|
int num_brush_patt = info.value<Serializer::Integer>("num_brush_patt");
|
|
int num_brushes = info.value<Serializer::Integer>("num_brushes");
|
|
|
|
|
|
std::string info_dump = info.str(0, "Info");
|
|
LOG("%s", info_dump.c_str());
|
|
|
|
auto pb = App::I->show_progress("Importing PPBR", 1 + num_brush_patt + num_brush_tips + num_brushes * 2);
|
|
|
|
// header image
|
|
Image header_image;
|
|
if (info.value<Serializer::Boolean>("has_header_image"))
|
|
sr >> header_image;
|
|
|
|
pb->increment();
|
|
|
|
// stroke previews
|
|
auto previews_count = sr.ru32();
|
|
for (int i = 0; i < previews_count; i++)
|
|
{
|
|
Image img;
|
|
sr >> img;
|
|
pb->increment();
|
|
}
|
|
|
|
// list of images
|
|
std::set<std::string> img_brushes;
|
|
std::set<std::string> img_patterns;
|
|
|
|
// brush tips
|
|
auto tips_count = sr.ru32();
|
|
for (int i = 0; i < tips_count; i++)
|
|
{
|
|
Image img;
|
|
sr >> img;
|
|
std::string path = App::I->data_path + "/brushes/" + img.file_name + "." + img.file_ext;
|
|
std::string path_thumb = App::I->data_path + "/brushes/thumbs/" + img.file_name + "." + img.file_ext;
|
|
if (!Asset::exist(path))
|
|
{
|
|
img.save_png(path);
|
|
auto thumb = img.resize(64, 64);
|
|
thumb.save_png(path_thumb);
|
|
}
|
|
else
|
|
{
|
|
LOG("import_ppbr: brush image already exists in %s", path.c_str());
|
|
}
|
|
img_brushes.insert(path);
|
|
pb->increment();
|
|
}
|
|
|
|
// brush patterns
|
|
auto patt_count = sr.ru32();
|
|
for (int i = 0; i < patt_count; i++)
|
|
{
|
|
Image img;
|
|
sr >> img;
|
|
std::string path = App::I->data_path + "/patterns/" + img.file_name + "." + img.file_ext;
|
|
std::string path_thumb = App::I->data_path + "/patterns/thumbs/" + img.file_name + "." + img.file_ext;
|
|
if (!Asset::exist(path))
|
|
{
|
|
img.save_png(path);
|
|
auto thumb = img.resize(64, 64);
|
|
thumb.save_png(path_thumb);
|
|
}
|
|
else
|
|
{
|
|
LOG("import_ppbr: brush image already exists in %s", path.c_str());
|
|
}
|
|
pb->increment();
|
|
img_patterns.insert(path);
|
|
}
|
|
|
|
// brush settings
|
|
auto brushes_count = sr.ru32();
|
|
for (int i = 0; i < brushes_count; i++)
|
|
{
|
|
auto b = std::make_shared<Brush>();
|
|
sr >> *b;
|
|
b->relocate_paths(App::I->data_path);
|
|
LOG("import_ppbr brush name %s", b->m_name.c_str());
|
|
if (b->valid())
|
|
{
|
|
for (auto p : s_panels)
|
|
p->add_brush(b);
|
|
}
|
|
pb->increment();
|
|
}
|
|
|
|
save();
|
|
pb->destroy();
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool NodePanelBrushPreset::import_abr(const std::string& path)
|
|
{
|
|
BT_SetTerminate();
|
|
|
|
ABR abr;
|
|
LOG("ABR detected");
|
|
|
|
std::string name, base, ext;
|
|
std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)");
|
|
std::smatch m;
|
|
if (!std::regex_search(path, m, r))
|
|
return false;
|
|
base = m[1].str();
|
|
name = m[2].str();
|
|
ext = m[3].str();
|
|
|
|
if (!str_iequals(ext, "abr") || !Asset::exist(path))
|
|
return false;
|
|
|
|
abr.open(path);
|
|
|
|
auto pb = App::I->show_progress("Importing ABR",
|
|
abr.m_samples.size() + abr.m_patterns.size() + abr.m_presets.size());
|
|
|
|
parallel_for(abr.m_samples.size(), [&](size_t i)
|
|
//for (const auto& samp : abr.m_samples)
|
|
{
|
|
auto ii = abr.m_samples.begin();
|
|
std::advance(ii, i);
|
|
const auto& samp = *ii;
|
|
std::string path_high = App::I->data_path + "/brushes/" + samp.first + ".png";
|
|
std::string path_thumb = App::I->data_path + "/brushes/thumbs/" + samp.first + ".png";
|
|
auto padded = samp.second->resize_squared(glm::u8vec4(255));
|
|
//auto high = padded.resize_power2();
|
|
//high.save(path_high);
|
|
samp.second->save_png(path_high);
|
|
auto thumb = padded.resize(64, 64);
|
|
thumb.save_png(path_thumb);
|
|
pb->increment();
|
|
});
|
|
|
|
parallel_for(abr.m_patterns.size(), [&](size_t i)
|
|
//for (const auto& patt : abr.m_patterns)
|
|
{
|
|
auto ii = abr.m_patterns.begin();
|
|
std::advance(ii, i);
|
|
const auto& patt = *ii;
|
|
std::string path_high = App::I->data_path + "/patterns/" + patt.first + ".png";
|
|
std::string path_thumb = App::I->data_path + "/patterns/thumbs/" + patt.first + ".png";
|
|
patt.second->save_png(path_high);
|
|
auto thumb = patt.second->resize(64, 64);
|
|
thumb.save_png(path_thumb);
|
|
pb->increment();
|
|
});
|
|
|
|
auto brushes = abr.compute_brushes(App::I->data_path);
|
|
App::I->ui_task([&]{
|
|
for (const auto& b : brushes)
|
|
{
|
|
if (b->valid())
|
|
{
|
|
LOG("add preset %s", b->m_name.c_str());
|
|
for (auto p : s_panels)
|
|
p->add_brush(b);
|
|
}
|
|
pb->increment();
|
|
}
|
|
});
|
|
|
|
save();
|
|
pb->destroy();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool NodePanelBrushPreset::import_brush(const std::string& path)
|
|
{
|
|
std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)");
|
|
std::smatch m;
|
|
if (!std::regex_search(path, m, r))
|
|
return false;
|
|
std::string base = m[1].str();
|
|
std::string name = m[2].str();
|
|
std::string ext = m[3].str();
|
|
|
|
return ext == "ppbr" ? import_ppbr(path) : import_abr(path);
|
|
}
|
|
|
|
void NodePanelBrushPreset::clear_brushes()
|
|
{
|
|
for (auto p : s_panels)
|
|
{
|
|
p->m_container->remove_all_children();
|
|
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
|
|
}
|
|
}
|
|
|
|
void NodePanelBrushPreset::added(Node* parent)
|
|
{
|
|
m_interacted = false;
|
|
m_notification->SetVisibility(m_container->m_children.size() == 0);
|
|
}
|