diff --git a/PanoPainter.vcxproj b/PanoPainter.vcxproj index cb31dca..0ccba43 100644 --- a/PanoPainter.vcxproj +++ b/PanoPainter.vcxproj @@ -370,6 +370,7 @@ + @@ -380,6 +381,7 @@ + @@ -500,6 +502,7 @@ + @@ -510,6 +513,7 @@ + diff --git a/PanoPainter.vcxproj.filters b/PanoPainter.vcxproj.filters index cdae9e9..53b13d0 100644 --- a/PanoPainter.vcxproj.filters +++ b/PanoPainter.vcxproj.filters @@ -384,6 +384,12 @@ Source Files + + Source Files\ui + + + Source Files\ui + @@ -638,6 +644,12 @@ Header Files + + Source Files\ui + + + Source Files\ui + diff --git a/data/dialogs/remote-page.xml b/data/dialogs/remote-page.xml new file mode 100644 index 0000000..44e1c8c --- /dev/null +++ b/data/dialogs/remote-page.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app_layout.cpp b/src/app_layout.cpp index 9055ca1..db52f95 100644 --- a/src/app_layout.cpp +++ b/src/app_layout.cpp @@ -9,6 +9,7 @@ #include "settings.h" #include "serializer.h" #include "font.h" +#include "node_remote_page.h" void App::title_update() { @@ -1333,6 +1334,32 @@ void App::initLayout() } } + auto whatsnew = std::make_shared(); + whatsnew->m_manager = &layout; + whatsnew->init(); + whatsnew->load_url("http://localhost:8080/app-content/whatsnew.xml", [this, whatsnew] (bool success) { + if (success) + { + int last_id = Settings::value_or("whatsnew-id", 0); + if (whatsnew->m_page_id <= g_version_build && whatsnew->m_page_id > last_id) + { + whatsnew->set_title(fmt::format("What's new in version {}", g_version_number)); + layout[main_id]->add_child(whatsnew); + } + whatsnew->add_button("Reload", 120, [this, whatsnew](Node*) { + whatsnew->reload(); + }); + whatsnew->add_button("Read Later", 120, [this, whatsnew](Node*) { + whatsnew->destroy(); + }); + whatsnew->add_button("Close", 100, [this, whatsnew](Node*) { + Settings::set("whatsnew-id", whatsnew->m_page_id); + Settings::save(); + whatsnew->destroy(); + }); + } + }); + brush_update(true, true); // hacky thing to make the toolbar buttons not steal events when moving cursor fast diff --git a/src/asset.cpp b/src/asset.cpp index a251831..7dd613c 100644 --- a/src/asset.cpp +++ b/src/asset.cpp @@ -186,6 +186,7 @@ bool Asset::open_url(const std::string& url, std::function progress curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &tmp_data); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler_asset); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); #ifdef __ANDROID__ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); #endif @@ -221,7 +222,7 @@ std::future& Asset::open_url_async(const std::string& url, m_stop_async = false; remote_future = std::async([this, url, progress, complete] { bool ok = open_url(url, progress); - if (ok && complete) + if (complete) complete(ok); return ok; }); diff --git a/src/layout.cpp b/src/layout.cpp index d532f60..05dec28 100644 --- a/src/layout.cpp +++ b/src/layout.cpp @@ -39,14 +39,28 @@ bool LayoutManager::load(const char* path) 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); + auto xml_string = std::string((char*)file.m_data, (size_t)file.m_len); file.close(); + + if (!parse(xml_string)) + return false; + + if (on_loaded) + on_loaded(m_loaded); + m_loaded = true; + + return true; +} + +bool LayoutManager::parse(const std::string& xml_string) noexcept +{ + auto old = std::move(m_layouts); + + tinyxml2::XMLDocument xml; + auto ret = xml.Parse(xml_string.c_str(), xml_string.size()); if (ret != tinyxml2::XMLError::XML_SUCCESS) { return false; @@ -70,7 +84,7 @@ bool LayoutManager::load(const char* path) if (std::find(osv.begin(), osv.end(), PP_OS) == osv.end()) { LOG("Layout %s not for this os(%s), skipping", id_str, PP_OS) - current = current->NextSiblingElement("layout"); + current = current->NextSiblingElement("layout"); continue; } } @@ -105,11 +119,6 @@ bool LayoutManager::load(const char* path) } current = current->NextSiblingElement("layout"); } - - if (on_loaded) - on_loaded(m_loaded); - m_loaded = true; - return true; } diff --git a/src/layout.h b/src/layout.h index 85c0c28..669416b 100644 --- a/src/layout.h +++ b/src/layout.h @@ -1,15 +1,4 @@ #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 -#include class LayoutManager { @@ -23,6 +12,7 @@ public: void unload(); void create(); bool load(const char* path); + bool parse(const std::string& xml_string) noexcept; bool reload(); class Node* operator[](uint16_t id) { diff --git a/src/main.cpp b/src/main.cpp index 3540d4a..8607fe3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1177,6 +1177,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) } case WM_ACTIVATE: { + win32_show_cursor(true); App::I->ui_task_async([=] { int active = GET_WM_ACTIVATE_STATE(wp, lp); WacomTablet::I.set_focus(active); diff --git a/src/node.cpp b/src/node.cpp index 67437fc..58ce054 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -37,6 +37,7 @@ #include "node_panel_quick.h" #include "node_tool_bucket.h" #include "node_panel_animation.h" +#include "node_metadata.h" void Node::app_redraw() { @@ -389,6 +390,16 @@ std::shared_ptr Node::load_template(const std::string& filename, const std return ret; } +std::shared_ptr Node::parse_template(const std::string& xml_string, const std::string& name) const +{ + LayoutManager m; + std::shared_ptr ret; + if (m.parse(xml_string)) + ret = m.get_ref(name.c_str())->m_children[0]->clone(); + m.unload(); + return ret; +} + void Node::add_child(Node* n) { App::I->ui_task([&] @@ -749,6 +760,8 @@ Node::Node(Node&& o) m_pos_offset_childred = o.m_pos_offset_childred; m_clip_uncut = o.m_clip_uncut; + auto_width = o.auto_width; + auto_height = o.auto_height; } Node::~Node() @@ -764,12 +777,14 @@ void Node::SetWidth(float value) { YGNodeStyleSetWidth(y_node, value); m_size.x = value; + auto_width = value == YGUndefined; app_redraw(); } void Node::SetWidthP(float value) { YGNodeStyleSetWidthPercent(y_node, value); + auto_width = value == YGUndefined; app_redraw(); } @@ -777,12 +792,14 @@ void Node::SetHeight(float value) { YGNodeStyleSetHeight(y_node, value); m_size.y = value; + auto_height = value == YGUndefined; app_redraw(); } void Node::SetHeightP(float value) { YGNodeStyleSetHeightPercent(y_node, value); + auto_height = value == YGUndefined; app_redraw(); } @@ -1163,6 +1180,7 @@ void Node::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) { YGNodeStyleSetWidth(y_node, YGUndefined); YGNodeStyleSetWidthPercent(y_node, YGUndefined); + auto_width = true; } else { @@ -1170,6 +1188,7 @@ void Node::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) YGNodeStyleSetWidthPercent(y_node, attr->FloatValue()); else YGNodeStyleSetWidth(y_node, attr->FloatValue()); + auto_width = false; } break; case kAttribute::MinWidth: @@ -1183,6 +1202,7 @@ void Node::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) { YGNodeStyleSetHeight(y_node, YGUndefined); YGNodeStyleSetHeightPercent(y_node, YGUndefined); + auto_height = true; } else { @@ -1190,6 +1210,7 @@ void Node::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) YGNodeStyleSetHeightPercent(y_node, attr->FloatValue()); else YGNodeStyleSetHeight(y_node, attr->FloatValue()); + auto_height = false; } break; case kAttribute::MinHeight: @@ -1416,6 +1437,7 @@ void Node::load_internal(const tinyxml2::XMLElement* x_node) CASE(kWidget::UserManual, NodeUserManual); CASE(kWidget::ToolBucket, NodeToolBucket); CASE(kWidget::Timeline, NodeAnimationTimeline); + CASE(kWidget::Metadata, NodeMetadata); #undef CASE case kWidget::Ref: { @@ -1481,6 +1503,9 @@ void Node::clone_copy(Node* dest) const dest->m_pos_offset = m_pos_offset; dest->m_pos_offset_childred = m_pos_offset_childred; dest->m_clip_uncut = m_clip_uncut; + + dest->auto_width = auto_width; + dest->auto_height = auto_height; } void Node::clone_children(Node* dest) const diff --git a/src/node.h b/src/node.h index 73b4062..475f3f2 100644 --- a/src/node.h +++ b/src/node.h @@ -97,6 +97,7 @@ enum class kWidget : uint16_t UserManual = const_hash("usermanual"), ToolBucket = const_hash("tool-bucket"), Timeline = const_hash("timeline"), + Metadata = const_hash("metadata"), }; class Node : public std::enable_shared_from_this @@ -143,6 +144,9 @@ public: // it's actually rendering bool m_on_screen = false; + bool auto_width = true; + bool auto_height = true; + Node(const Node&) = delete; Node& operator=(const Node&) = delete; Node&& operator=(Node&& o); @@ -279,6 +283,15 @@ public: } return nullptr; } + template std::shared_ptr add_child_xml(const std::string& xml_string, const std::string& name) + { + if (auto t = parse_template(xml_string, name)) + { + add_child(t); + return std::dynamic_pointer_cast(t); + } + return nullptr; + } virtual void on_tick(float dt) { }; virtual kEventResult on_event(Event* e); @@ -296,6 +309,7 @@ public: const Node* init_template(const char* id); bool init_template_file(const std::string& path, const std::string& id); std::shared_ptr load_template(const std::string& filename, const std::string& name) const; + std::shared_ptr parse_template(const std::string& xml_stirng, const std::string& name) const; void tick(float dt); void app_redraw(); void add_child(Node* n); diff --git a/src/node_metadata.cpp b/src/node_metadata.cpp new file mode 100644 index 0000000..94b02f1 --- /dev/null +++ b/src/node_metadata.cpp @@ -0,0 +1,25 @@ +#include "pch.h" +#include "log.h" +#include "node_metadata.h" + +NodeMetadata::NodeMetadata() noexcept +{ + m_nodeID = const_hash("metadata"); + m_nodeID_s = "metadata"; +} + +Node* NodeMetadata::clone_instantiate() const +{ + return new NodeMetadata(); +} +void NodeMetadata::clone_copy(Node* dest) const +{ + Node::clone_copy(dest); + NodeMetadata* n = static_cast(dest); + n->m_props = m_props; +} +void NodeMetadata::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) +{ + Node::parse_attributes(ka, attr); + m_props[attr->Name()] = attr->Value(); +} diff --git a/src/node_metadata.h b/src/node_metadata.h new file mode 100644 index 0000000..2861ece --- /dev/null +++ b/src/node_metadata.h @@ -0,0 +1,12 @@ +#pragma once +#include "node.h" + +class NodeMetadata : public Node +{ +public: + std::map m_props; + NodeMetadata() noexcept; + 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; +}; diff --git a/src/node_remote_page.cpp b/src/node_remote_page.cpp new file mode 100644 index 0000000..7131704 --- /dev/null +++ b/src/node_remote_page.cpp @@ -0,0 +1,98 @@ +#include "pch.h" +#include "log.h" +#include "node_remote_page.h" +#include "node_button.h" +#include "node_scroll.h" +#include "asset.h" +#include "node_text.h" +#include "node_metadata.h" + +Node* NodeRemotePage::clone_instantiate() const +{ + return new NodeRemotePage(); +} + +void NodeRemotePage::clone_copy(Node* dest) const +{ + auto n = static_cast(dest); + n->m_page_id = m_page_id; + n->m_loaded = m_loaded; +} + +void NodeRemotePage::init() +{ + init_template_file("data/dialogs/remote-page.xml", "remote-page"); + init_controls(); +} + +void NodeRemotePage::init_controls() +{ + m_content = find("content"); + m_title = find("title"); +} + +std::future NodeRemotePage::load_url(const std::string& url, + std::function on_complete /*= nullptr*/) noexcept +{ + m_loading = true; + + auto align = m_content->add_child_ref(); + align->SetWidthP(100.f); + align->SetHeightP(100.f); + align->SetAlign(YGAlignCenter); + align->SetJustify(YGJustifyCenter); + auto text = align->add_child_ref(); + text->set_font(kFont::Arial_30); + text->set_text("Connecting to the server..."); + + m_url = url; + m_loaded = false; + auto remote = std::make_shared(); + auto prom = std::make_shared>(); + remote->open_url_async(url, nullptr, [this, remote, align, text, prom, on_complete](bool success) { + if (success) + { + m_content->add_child_xml(std::string((char*)remote->m_data, (size_t)remote->m_len), "about"); + if (auto meta = m_content->find("metadata")) + m_page_id = std::stol(meta->m_props["page-id"]); + align->destroy(); + } + else + { + text->set_text("Error loading the page"); + } + m_loaded = success; + m_loading = false; + prom->set_value(success); + if (on_complete) + on_complete(success); + }); + return prom->get_future(); +} + +void NodeRemotePage::reload() noexcept +{ + if (m_loading) + return; + m_content->remove_all_children(); + load_url(m_url); +} + +void NodeRemotePage::set_title(const std::string& title) +{ + m_title->set_text(title); +} + +void NodeRemotePage::add_button(const std::string& label, int width, + std::function onclic /*= nullptr*/) noexcept +{ + if (auto footer = find("footer")) + { + auto b = footer->add_child(); + b->m_text->set_text(label); + b->m_border->SetWidth(width); + b->m_border->SetHeight(30); + b->m_border->SetMargin(5, 0, 0, 0); + b->on_click = onclic; + } +} diff --git a/src/node_remote_page.h b/src/node_remote_page.h new file mode 100644 index 0000000..fc368fb --- /dev/null +++ b/src/node_remote_page.h @@ -0,0 +1,23 @@ +#pragma once +#include "node_border.h" + +class NodeRemotePage : public NodeBorder +{ + class NodeScroll* m_content; + class NodeText* m_title; + bool m_loading; + std::string m_url; +public: + bool m_loaded; + int m_page_id; + virtual Node* clone_instantiate() const override; + virtual void clone_copy(Node* dest) const override; + virtual void init() override; + void init_controls(); + std::future load_url(const std::string& url, + std::function on_complete = nullptr) noexcept; + void reload() noexcept; + void set_title(const std::string& title); + void add_button(const std::string& label, int width, + std::function onclic = nullptr) noexcept; +}; diff --git a/src/node_text.cpp b/src/node_text.cpp index 42312d1..6f6054e 100644 --- a/src/node_text.cpp +++ b/src/node_text.cpp @@ -20,6 +20,10 @@ void NodeText::clone_copy(Node* dest) const n->m_color = m_color; n->m_font_size = m_font_size; n->font_id = font_id; + n->m_multiline = m_multiline; + n->m_off = m_off; + n->m_text_align_v = m_text_align_v; + n->m_text_align_h = m_text_align_h; } void NodeText::create() @@ -32,7 +36,7 @@ void NodeText::create() font_id = (kFont)const_hash(font); m_text_mesh.create(); m_text_mesh.update(font_id, m_text); - SetSize(m_text_mesh.bb); + update_layout(); } } @@ -41,13 +45,7 @@ void NodeText::set_font(kFont fontID) font_id = fontID; m_text_mesh.create(); m_text_mesh.update(font_id, m_text); - SetSize(m_text_mesh.bb); -} - -void NodeText::restore_context() -{ - Node::restore_context(); - create(); + update_layout(); } void NodeText::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) @@ -55,6 +53,25 @@ void NodeText::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* att Node::parse_attributes(ka, attr); switch (ka) { + case kAttribute::Multiline: + m_multiline = attr->BoolValue(); + break; + case kAttribute::TextAlign: + if (strcmp(attr->Value(), "left") == 0) + m_text_align_h = TextAlign::Begin; + else if (strcmp(attr->Value(), "center") == 0) + m_text_align_h = TextAlign::Center; + else if (strcmp(attr->Value(), "right") == 0) + m_text_align_h = TextAlign::End; + break; + case kAttribute::TextVerticalAlign: + if (strcmp(attr->Value(), "top") == 0) + m_text_align_v = TextAlign::Begin; + else if (strcmp(attr->Value(), "center") == 0) + m_text_align_v = TextAlign::Center; + else if (strcmp(attr->Value(), "bottom") == 0) + m_text_align_v = TextAlign::End; + break; case kAttribute::TextWrapWidth: m_text_mesh.max_width = attr->IntValue(); break; @@ -82,11 +99,11 @@ void NodeText::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* att } } -void NodeText::set_text(const char* s) +void NodeText::set_text(const std::string& s) { m_text = s; m_text_mesh.update(font_id, s); - SetSize(m_text_mesh.bb); + update_layout(); } void NodeText::set_text_format(const char* fmt, ...) @@ -98,7 +115,39 @@ void NodeText::set_text_format(const char* fmt, ...) va_end(args); m_text = buffer; m_text_mesh.update(font_id, buffer); - SetSize(m_text_mesh.bb); + update_layout(); +} + +void NodeText::update_layout() +{ + if (auto_width) + { + YGNodeStyleSetWidth(y_node, m_text_mesh.bb.x); + m_size.x = m_text_mesh.bb.x; + } + if (auto_height) + { + YGNodeStyleSetHeight(y_node, m_text_mesh.bb.y); + m_size.y = m_text_mesh.bb.y; + } + + auto pad = GetPadding(); + float h = GetHeight() - (pad[1] + pad[3]); + float w = GetWidth() - (pad[0] + pad[2]); + + if (m_text_align_v == TextAlign::Begin) + m_off.y = pad[0]; + else if (m_text_align_v == TextAlign::Center) + m_off.y = (h - m_text_mesh.bb.y) * 0.5f + pad[0]; + else + m_off.y = (h - m_text_mesh.bb.y) + pad[0]; + + if (m_text_align_h == TextAlign::Begin) + m_off.x = pad[3]; + else if (m_text_align_v == TextAlign::Center) + m_off.x = (w - m_text_mesh.bb.x) * 0.5f + pad[3]; + else + m_off.x = (w - m_text_mesh.bb.x) + pad[3]; } void NodeText::draw() @@ -116,6 +165,8 @@ void NodeText::draw() void NodeText::handle_resize(glm::vec2 old_size, glm::vec2 new_size, float zoom) { + auto pad = GetPadding(); + m_text_mesh.max_width = m_multiline ? new_size.x - (pad[1] + pad[3]) : 0; m_text_mesh.update(font_id, m_text); - SetSize(m_text_mesh.bb); + update_layout(); } diff --git a/src/node_text.h b/src/node_text.h index f908003..4f4530a 100644 --- a/src/node_text.h +++ b/src/node_text.h @@ -5,20 +5,33 @@ class NodeText : public Node { public: + enum class TextAlign + { + Begin, + Center, + End, + }; TextMesh m_text_mesh; std::string m_text; std::string m_font = "arial"; glm::vec4 m_color{ 1, 1, 1, 1 }; int m_font_size = 11; kFont font_id; + + bool m_multiline = false; + glm::vec2 m_off; + TextAlign m_text_align_v = TextAlign::Center; + TextAlign m_text_align_h = TextAlign::Begin; + 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_text_format(const char* fmt, ...); - void set_font(kFont fontID); virtual void draw() override; virtual void handle_resize(glm::vec2 old_size, glm::vec2 new_size, float zoom) override; + + void set_text(const std::string& s); + void set_text_format(const char* fmt, ...); + void set_font(kFont fontID); + void update_layout(); }; diff --git a/src/node_text_input.cpp b/src/node_text_input.cpp index 5765351..620c496 100644 --- a/src/node_text_input.cpp +++ b/src/node_text_input.cpp @@ -33,9 +33,6 @@ void NodeTextInput::clone_copy(Node* dest) const { NodeBorder::clone_copy(dest); NodeTextInput* n = static_cast(dest); - - n->m_multiline = m_multiline; - n->m_text_mesh.max_width = m_text_mesh.max_width; n->m_text_mesh.create(); n->m_text_mesh.update(font_id, m_text); @@ -44,6 +41,7 @@ void NodeTextInput::clone_copy(Node* dest) const n->m_color = m_color; n->m_font_size = m_font_size; n->font_id = font_id; + n->m_multiline = m_multiline; n->m_off = m_off; n->m_text_align_v = m_text_align_v; n->m_text_align_h = m_text_align_h; diff --git a/src/node_text_input.h b/src/node_text_input.h index 3a41af5..1994646 100644 --- a/src/node_text_input.h +++ b/src/node_text_input.h @@ -20,6 +20,7 @@ public: glm::vec4 m_color{ 1, 1, 1, 1 }; int m_font_size = 11; kFont font_id; + bool m_multiline = false; bool m_cursor_visible = false; glm::vec2 m_off;