diff --git a/src/asset.cpp b/src/asset.cpp index b750857..697f51f 100644 --- a/src/asset.cpp +++ b/src/asset.cpp @@ -150,6 +150,79 @@ bool Asset::create_dir(const std::string& path) #endif } +static size_t curl_data_handler_asset(void* contents, size_t size, size_t nmemb, void* userp) +{ + auto buffer = reinterpret_cast*>(userp); + buffer->insert(buffer->end(), (char*)contents, (char*)contents + (size * nmemb)); + return size * nmemb; +} + +static int progress_callback_asset_download(void* clientp, curl_off_t dltotal, + curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) +{ + Asset* ass = static_cast(clientp); + if (ass->m_stop_async) + { + LOG("interrupt download of %s", ass->m_current_url.c_str()); + return 1; + } + float p = dltotal > 0 ? (float)dlnow / (float)dltotal : 0.f; + bool cont = ass->on_progress(p); + return cont ? 0 : 1; +} + +bool Asset::open_url(const std::string& url, std::function progress /*= nullptr*/) +{ + CURL* curl = curl_easy_init(); + if (curl) + { + close(); + LOG("download %s", url.c_str()); + m_current_url = url; + std::vector data; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler_asset); +#ifdef __ANDROID__ + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); +#endif + if (progress) + { + on_progress = progress; + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback_asset_download); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + } + auto err = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + if (err == CURLE_OK) + { + if (m_data) + delete m_data; + m_data = new uint8_t[data.size()]; + std::copy(data.begin(), data.end(), m_data); + m_len = data.size(); + return true; + } + } + return false; +} + +std::future& Asset::open_url_async(const std::string& url, + std::function progress /*= nullptr*/, std::function complete /*= nullptr*/) +{ + close_remote(); + m_stop_async = false; + remote_future = std::async([this, url, progress, complete] { + bool ok = open_url(url, progress); + if (ok && complete) + complete(ok); + return ok; + }); + return remote_future; +} + bool Asset::open(const char* path) { //LOG("Asset::open %s", path); @@ -249,3 +322,18 @@ void Asset::close() m_fp = nullptr; #endif } + +void Asset::close_remote() +{ + if (remote_future.valid()) + { + m_stop_async = true; + remote_future.get(); + } +} + +Asset::~Asset() +{ + close_remote(); + close(); +} diff --git a/src/asset.h b/src/asset.h index 30aa6b0..110a360 100644 --- a/src/asset.h +++ b/src/asset.h @@ -13,13 +13,23 @@ public: static std::string absolute(const std::string& path); static bool is_asset(const std::string& path); static bool create_dir(const std::string& path); + + bool m_stop_async = false; + std::function on_progress; + std::future remote_future; + std::string m_current_path; + std::string m_current_url; FILE* m_fp = nullptr; int m_len = 0; uint8_t* m_data = nullptr; bool open(const char* path); + bool open_url(const std::string& url, std::function progress = nullptr); + std::future& open_url_async(const std::string& url, std::function progress = nullptr, + std::function complete = nullptr); uint8_t* read_all(); void close(); - ~Asset() { close(); } + void close_remote(); + ~Asset(); }; diff --git a/src/node.cpp b/src/node.cpp index 51b7cf9..8e389e2 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -321,7 +321,8 @@ void Node::loaded() void Node::added(Node* parent) { - + for (auto& c : m_children) + c->added(this); } void Node::removed(Node* parent) diff --git a/src/node.h b/src/node.h index fb6a861..adfb8e0 100644 --- a/src/node.h +++ b/src/node.h @@ -31,6 +31,7 @@ enum class kAttribute : uint16_t Justify = const_hash("justify"), Align = const_hash("align"), Path = const_hash("path"), + Url = const_hash("url"), Region = const_hash("region"), Position = const_hash("position"), Positioning = const_hash("positioning"), diff --git a/src/node_image.cpp b/src/node_image.cpp index 0c41871..2ca3b1b 100644 --- a/src/node_image.cpp +++ b/src/node_image.cpp @@ -2,6 +2,7 @@ #include "log.h" #include "node_image.h" #include "shader.h" +#include "app.h" Plane NodeImage::m_plane; Sampler NodeImage::m_sampler; @@ -30,6 +31,9 @@ void NodeImage::clone_copy(Node* dest) const n->m_sz = m_sz; n->m_path = m_path; n->m_tex_id = m_tex_id; + n->m_url = m_url; + n->m_remote_texture = m_remote_texture; + n->m_autosize = m_autosize; } void NodeImage::create() @@ -64,6 +68,9 @@ void NodeImage::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* at m_path = attr->Value(); m_tex_id = const_hash(attr->Value()); break; + case kAttribute::Url: + m_url = attr->Value(); + break; case kAttribute::Region: { glm::vec4 v; @@ -85,9 +92,13 @@ void NodeImage::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* at void NodeImage::draw() { - TextureManager::get(m_tex_id).bind(); + m_remote_texture ? + m_remote_texture->bind() : + 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) { @@ -103,7 +114,6 @@ void NodeImage::draw() ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp * glm::scale(glm::vec3(m_scale, 1.f))); m_plane.draw_fill(); sampler.unbind(); - TextureManager::get(m_tex_id).unbind(); glDisable(GL_BLEND); } @@ -122,3 +132,36 @@ bool NodeImage::set_image(const std::string& path) } return false; } + +void NodeImage::load_url(const std::string& url) +{ + m_remote_asset = std::make_shared(); + m_remote_asset->open_url_async(url, + [this] (float progress) -> bool { + return true; + }, + [this] (bool success) { + if (success) + { + int w, h, c; + uint8_t* rgba = stbi_load_from_memory(m_remote_asset->m_data, m_remote_asset->m_len, &w, &h, &c, 4); + m_remote_texture = std::make_shared(); + m_remote_texture->create(w, h, GL_RGBA8, GL_RGBA, rgba); + m_remote_texture->auto_destroy = true; + if (m_use_mipmaps) + m_remote_texture->create_mipmaps(); + delete rgba; + if (m_autosize) + SetAspectRatio(w / h); + } + m_remote_asset.reset(); + } + ); +} + +void NodeImage::added(Node* parent) +{ + Node::added(parent); + if (!m_url.empty() && root() == App::I->layout.get(App::I->main_id)) + load_url(m_url); +} diff --git a/src/node_image.h b/src/node_image.h index 3a32248..30c678a 100644 --- a/src/node_image.h +++ b/src/node_image.h @@ -17,7 +17,10 @@ public: glm::vec2 m_sz; glm::vec2 m_scale = { 1.f, 1.f }; std::string m_path; + std::string m_url; uint16_t m_tex_id; + std::shared_ptr m_remote_asset; + std::shared_ptr m_remote_texture; static void static_init(); virtual Node* clone_instantiate() const override; virtual void clone_copy(Node* dest) const override; @@ -25,5 +28,7 @@ public: virtual void restore_context() override; virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override; virtual void draw() override; + virtual void added(Node* parent) override; bool set_image(const std::string& path); + void load_url(const std::string& url); };