diff --git a/PanoPainter.vcxproj b/PanoPainter.vcxproj
index 15437a4..d16925e 100644
--- a/PanoPainter.vcxproj
+++ b/PanoPainter.vcxproj
@@ -570,6 +570,14 @@
+
+
+ true
+ true
+ true
+ true
+
+
diff --git a/PanoPainter.vcxproj.filters b/PanoPainter.vcxproj.filters
index f284f7f..bab1563 100644
--- a/PanoPainter.vcxproj.filters
+++ b/PanoPainter.vcxproj.filters
@@ -739,4 +739,9 @@
Resource Files
+
+
+ extras
+
+
\ No newline at end of file
diff --git a/data/layout.xml b/data/layout.xml
index 84f6085..c777d3c 100644
--- a/data/layout.xml
+++ b/data/layout.xml
@@ -1,5 +1,8 @@
-
+
@@ -60,15 +63,15 @@
-
+
-
+
-
+
-
+
@@ -2009,5 +2012,6 @@ Here's a list of what's available in this release.
-->
-
+
+
diff --git a/extra/layout.xsd b/extra/layout.xsd
new file mode 100644
index 0000000..11d2739
--- /dev/null
+++ b/extra/layout.xsd
@@ -0,0 +1,352 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app_dialogs.cpp b/src/app_dialogs.cpp
index 20832d3..de23d3b 100644
--- a/src/app_dialogs.cpp
+++ b/src/app_dialogs.cpp
@@ -113,7 +113,7 @@ void App::dialog_newdoc()
dialog->btn_ok->on_click = [this, dialog](Node*)
{
- std::string name = dialog->input->m_string;
+ std::string name = dialog->input->m_text;
std::string path = work_path + "/" + name + ".ppi";
if (name.empty())
@@ -381,7 +381,7 @@ void App::dialog_save()
dialog->btn_ok->on_click = [this, dialog](Node*)
{
- std::string name = dialog->input->m_string;
+ std::string name = dialog->input->m_text;
std::string path = work_path + "/" + name + ".ppi";
if (name.empty())
diff --git a/src/font.cpp b/src/font.cpp
index 804e454..a1a41c1 100644
--- a/src/font.cpp
+++ b/src/font.cpp
@@ -83,7 +83,7 @@ std::vector TextMesh::tokenize(const std::string& s, const Font
bool is_delim = std::find(delims.begin(), delims.end(), c) != delims.end();
wrap |= is_delim; // set wrap to notify a delimiter has been reached
// when a new non-delim char is detected, start a new token
- if (wrap && !is_delim)
+ if (wrap && !is_delim && !tmp.empty())
{
parts.push_back(tmp);
tmp.clear();
@@ -130,40 +130,61 @@ bool TextMesh::create()
return true;
}
-void TextMesh::update(kFont id, const char* text)
+void TextMesh::update(kFont id, const std::string& text)
{
font_id = id;
auto& f = FontManager::get(id);
+ float spacing = f.bounds.w - f.bounds.y;
+ float avg_width = f.bounds.z - f.bounds.x;
+ cur_box = glm::vec4(0, 0, 5, spacing);
+
+ glm::vec2 bbmin(FLT_MAX);
+ glm::vec2 bbmax(-FLT_MAX);
+ if (text.empty())
+ {
+ bbmin = { 0, -spacing * 0.5f };
+ bbmax = { 1, +spacing * 0.5f };
+ }
+
if (f.chars.size())
{
float x = 0;
float y = 0;
std::vector v;
std::vector idx;
- glm::vec2 bbmin(FLT_MAX);
- glm::vec2 bbmax(-FLT_MAX);
std::vector parts = max_width > 0 ?
tokenize(text, f) :
std::vector{ Token(text, 0.f) };
+ if (parts.empty())
+ {
+ bbmin = { 0, -spacing * 0.5f };
+ bbmax = { 1, +spacing * 0.5f };
+ }
+
for (auto p : parts)
{
- if (max_width > 0 && x + p.w > max_width * f.scale)
+ if (max_width > 0 && (x + p.w) * f.scale > max_width)
{
x = 0;
- y += f.size * f.scale;
+ y += spacing;
}
for (char c : p.s)
{
if (c == '\n')
{
x = 0;
- y += f.size * f.scale;
+ y += spacing;
continue;
}
stbtt_aligned_quad q;
stbtt_GetBakedQuad((stbtt_bakedchar*)f.chars.data(), f.w, f.h, c - f.start_char, &x, &y, &q, true);
+ if (max_width > 0 && x * f.scale > max_width /*font scale factor*/)
+ {
+ x = 0;
+ y += spacing;
+ }
auto n = (int)v.size();
v.emplace_back(q.x0 / f.scale, q.y1 / f.scale, q.s0, q.t1);
v.emplace_back(q.x0 / f.scale, q.y0 / f.scale, q.s0, q.t0);
@@ -177,6 +198,7 @@ void TextMesh::update(kFont id, const char* text)
idx.push_back(n + 3);
bbmin = glm::min(bbmin, { q.x0 / f.scale, q.y0 / f.scale });
bbmax = glm::max(bbmax, { q.x1 / f.scale, q.y1 / f.scale });
+ cur_box = glm::vec4(x, y, 5, spacing);
}
}
for (auto& vi : v)
diff --git a/src/font.h b/src/font.h
index f411d36..6e70d6e 100644
--- a/src/font.h
+++ b/src/font.h
@@ -58,7 +58,8 @@ public:
GLuint font_buffers[2] = {0, 0};
kFont font_id;
glm::vec2 bb = { 0, 0 };
+ glm::vec4 cur_box;
bool create();
- void update(kFont id, const char* text);
+ void update(kFont id, const std::string& text);
void draw();
};
diff --git a/src/node.cpp b/src/node.cpp
index 1aecbce..0fc32fd 100644
--- a/src/node.cpp
+++ b/src/node.cpp
@@ -1036,7 +1036,7 @@ void Node::update_internal(const glm::vec2& origin, const glm::mat4& proj, float
m_mvp = proj * pos * scale * pivot * prescale;
m_proj = proj;
- if (m_size != old_size || m_zoom != zoom)
+ if (!glm::any(glm::isnan(m_size)) && m_size != old_size || m_zoom != zoom)
{
m_zoom = zoom;
handle_resize(old_size, m_size, zoom);
diff --git a/src/node.h b/src/node.h
index 3ca96c4..52e55d9 100644
--- a/src/node.h
+++ b/src/node.h
@@ -48,6 +48,9 @@ enum class kAttribute : uint16_t
AutoSize = const_hash("autosize"),
MouseCapture = const_hash("mouse-capture"),
ScrollColor = const_hash("scroll-color"),
+ Multiline = const_hash("multiline"),
+ TextAlign = const_hash("text-align"),
+ TextVerticalAlign = const_hash("text-vertical-align"),
};
enum class kWidget : uint16_t
diff --git a/src/node_dialog_layer_rename.cpp b/src/node_dialog_layer_rename.cpp
index bec72da..07cd302 100644
--- a/src/node_dialog_layer_rename.cpp
+++ b/src/node_dialog_layer_rename.cpp
@@ -43,5 +43,5 @@ void NodeDialogLayerRename::loaded()
std::string NodeDialogLayerRename::get_name()
{
- return input ? input->m_string : "";
+ return input ? input->m_text : "";
}
diff --git a/src/node_input_box.cpp b/src/node_input_box.cpp
index 347b891..02b8cfc 100644
--- a/src/node_input_box.cpp
+++ b/src/node_input_box.cpp
@@ -22,7 +22,7 @@ void NodeInputBox::init()
btn_ok = m_template->find("btn-ok");
btn_ok->on_click = [&](Node*) {
if (on_submit)
- on_submit(this, m_field_text->m_text->m_text);
+ on_submit(this, m_field_text->m_text);
};
btn_cancel = m_template->find("btn-cancel");
btn_cancel->on_click = [&](Node*) { destroy(); };
@@ -39,7 +39,7 @@ kEventResult NodeInputBox::handle_event(Event* e)
break;
case kEventType::KeyUp:
if (ke->m_key == kKey::KeyEnter && on_submit)
- on_submit(this, m_field_text->m_text->m_text);
+ on_submit(this, m_field_text->m_text);
break;
case kEventType::KeyChar:
break;
diff --git a/src/node_text.cpp b/src/node_text.cpp
index ce11c96..f3305f7 100644
--- a/src/node_text.cpp
+++ b/src/node_text.cpp
@@ -14,7 +14,7 @@ void NodeText::clone_copy(Node* dest) const
NodeText* n = static_cast(dest);
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.c_str());
+ n->m_text_mesh.update(font_id, m_text);
n->m_text = m_text;
n->m_font = m_font;
n->m_color = m_color;
@@ -31,7 +31,7 @@ void NodeText::create()
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());
+ m_text_mesh.update(font_id, m_text);
SetSize(m_text_mesh.bb);
}
}
@@ -40,7 +40,7 @@ void NodeText::set_font(kFont fontID)
{
font_id = fontID;
m_text_mesh.create();
- m_text_mesh.update(font_id, m_text.c_str());
+ m_text_mesh.update(font_id, m_text);
SetSize(m_text_mesh.bb);
}
@@ -116,5 +116,6 @@ void NodeText::draw()
void NodeText::handle_resize(glm::vec2 old_size, glm::vec2 new_size, float zoom)
{
- m_text_mesh.update(font_id, m_text.c_str());
+ if (old_size != new_size)
+ m_text_mesh.update(font_id, m_text);
}
diff --git a/src/node_text_input.cpp b/src/node_text_input.cpp
index d435d6e..97f14e7 100644
--- a/src/node_text_input.cpp
+++ b/src/node_text_input.cpp
@@ -14,53 +14,39 @@ void NodeTextInput::on_tick(float dt)
timer += dt;
bool focus = root()->current_key_capture.get() == this;
- if (m_cursor && !focus)
- m_cursor->m_display = false;
+ if (!focus)
+ m_cursor_visible = false;
m_thinkness = focus;
if (timer > 1.0)
{
timer = 0;
- if (focus && m_cursor)
+ if (focus)
{
- m_cursor->m_display = !m_cursor->m_display;
+ m_cursor_visible = !m_cursor_visible;
app_redraw();
}
}
}
-void NodeTextInput::clone_finalize(Node* dest) const
+void NodeTextInput::clone_copy(Node* dest) const
{
NodeBorder::clone_copy(dest);
NodeTextInput* n = static_cast(dest);
- if (n->m_children.size())
- {
- n->m_text = (NodeText*)n->m_children[0].get();
- n->m_cursor = (NodeBorder*)n->m_children[1].get();
- }
- n->m_string = m_string;
-}
+ n->m_multiline = m_multiline;
-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";
- YGNodeStyleSetFlexDirection(y_node, YGFlexDirectionRow);
- m_cursor = add_child();
- m_cursor->SetWidth(4);
- m_cursor->SetHeightP(100);
- m_cursor->SetMargin(0, 0, 0, 2);
+ 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);
+ 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;
+ n->m_off = m_off;
+ n->m_text_align_v = m_text_align_v;
+ n->m_text_align_h = m_text_align_h;
}
kEventResult NodeTextInput::handle_event(Event* e)
@@ -74,15 +60,14 @@ kEventResult NodeTextInput::handle_event(Event* e)
case kEventType::MouseUpL:
key_capture();
timer = 0;
- if (m_cursor)
- m_cursor->m_display = true;
+ m_cursor_visible = true;
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());
+ // m_text->set_text(m_string);
// break;
//default:
// break;
@@ -93,38 +78,42 @@ kEventResult NodeTextInput::handle_event(Event* e)
on_return(this);
if (ke->m_key == kKey::KeyBackspace)
{
- if (!m_string.empty())
+ if (!m_text.empty())
{
- m_string.erase(m_string.end() - 1);
- m_text->set_text(m_string.c_str());
+ m_text.erase(m_text.end() - 1);
+ set_text(m_text);
}
}
break;
case kEventType::KeyChar:
- if (m_cursor)
- {
- timer = 0;
- m_cursor->m_display = true;
- }
+ timer = 0;
+ m_cursor_visible = true;
if (ke->m_char == '\b' // backspace
|| ke->m_char == 0x7f // DEL
)
{
- if (!m_string.empty())
+ if (!m_text.empty())
{
- m_string.erase(m_string.end() - 1);
- m_text->set_text(m_string.c_str());
+ m_text.erase(m_text.end() - 1);
+ set_text(m_text);
}
}
else if (ke->m_char == '\n' || ke->m_char == '\r') // enter/return
{
- if (on_return)
+ if (m_multiline)
+ {
+ m_text += '\n';
+ set_text(m_text);
+ }
+ else 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());
+ m_text += (char)ke->m_char;
+ set_text(m_text);
}
break;
default:
@@ -136,9 +125,117 @@ kEventResult NodeTextInput::handle_event(Event* e)
void NodeTextInput::set_text(const std::string& s)
{
- if (m_text)
- m_text->set_text(s.c_str());
- m_string = s;
+ m_text = s;
+ m_text_mesh.update(font_id, s);
+ update_layout();
+}
+
+void NodeTextInput::set_text_format(const char* fmt, ...)
+{
+ static char buffer[4096];
+ va_list args;
+ va_start(args, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, args);
+ va_end(args);
+ m_text = buffer;
+ m_text_mesh.update(font_id, buffer);
+ update_layout();
+}
+
+void NodeTextInput::update_layout()
+{
+ 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 NodeTextInput::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;
+ update_layout();
+}
+
+void NodeTextInput::create()
+{
+ NodeBorder::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);
+ update_layout();
+ }
+}
+
+void NodeTextInput::draw()
+{
+ NodeBorder::draw();
+
+ glm::mat4 pos = glm::translate(glm::vec3(glm::floor(m_pos + m_off), 0));
+ ShaderManager::use(kShader::Font);
+ ShaderManager::u_int(kShaderUniform::Tex, 0);
+ ShaderManager::u_mat4(kShaderUniform::MVP, m_proj * pos);
+ ShaderManager::u_vec4(kShaderUniform::Col, m_color);
+ glEnable(GL_BLEND);
+ m_text_mesh.draw();
+ glDisable(GL_BLEND);
+
+ if (m_cursor_visible)
+ {
+ glDisable(GL_BLEND);
+ glm::mat4 cur_pos = glm::translate(glm::vec3(m_pos + m_off + xy(m_text_mesh.cur_box), 0));
+ glm::mat4 cur_scale = glm::scale(glm::vec3(zw(m_text_mesh.cur_box), 1));
+ glm::mat4 cur_pivot = glm::translate(glm::vec3(0.5, 0.5, 0));
+ ShaderManager::use(kShader::Color);
+ ShaderManager::u_mat4(kShaderUniform::MVP, m_proj * cur_pos * cur_scale * cur_pivot);
+ ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 });
+ m_plane.draw_fill();
+ }
+}
+
+void NodeTextInput::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* 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;
+ default:
+ NodeBorder::parse_attributes(ka, attr);
+ }
}
void NodeTextInput::destroy()
diff --git a/src/node_text_input.h b/src/node_text_input.h
index 00375b6..1395a5c 100644
--- a/src/node_text_input.h
+++ b/src/node_text_input.h
@@ -1,22 +1,44 @@
#pragma once
#include "node_border.h"
#include "node_text.h"
+#include "shape.h"
class NodeTextInput : public NodeBorder
{
-public:
float timer = 0;
- NodeText* m_text;
- NodeBorder* m_cursor;
- NodeBorder* m_border;
- std::string m_string;
+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;
+ bool m_cursor_visible = false;
+ glm::vec2 m_off;
+ TextAlign m_text_align_v = TextAlign::Center;
+ TextAlign m_text_align_h = TextAlign::Begin;
+
std::function on_return;
+
virtual Node* clone_instantiate() const override;
- virtual void clone_finalize(Node* dest) const override;
- virtual void init() override;
+ virtual void clone_copy(Node* dest) const override;
virtual void on_tick(float dt) override;
virtual kEventResult handle_event(Event* e) override;
virtual void destroy() override;
- void init_controls();
+ virtual void parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr) override;
+ virtual void draw() override;
+ virtual void create() 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 update_layout();
};