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;