improve text input

This commit is contained in:
2019-09-17 13:57:37 +02:00
parent 882a516455
commit 72fa400651
14 changed files with 598 additions and 83 deletions

View File

@@ -570,6 +570,14 @@
<ItemGroup>
<Image Include="icon.ico" />
</ItemGroup>
<ItemGroup>
<Xsd Include="extra\layout.xsd">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</Xsd>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>

View File

@@ -739,4 +739,9 @@
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
<ItemGroup>
<Xsd Include="extra\layout.xsd">
<Filter>extras</Filter>
</Xsd>
</ItemGroup>
</Project>

View File

@@ -1,5 +1,8 @@
<?xml version="1.0"?>
<root>
<root
xmlns="http://panopainter.com/layout.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<!--brush icon-->
<layout id="tpl-brush-icon">
@@ -60,15 +63,15 @@
<node grow="1">
<node dir="row" height="30" align="center" grow="1">
<text text="Author" margin="0 5 0 0"/>
<text-input align="center" pad="5" grow="1" height="30" color=".3"/>
<text-input pad="5" grow="1" height="30" color=".3"/>
</node>
<node dir="row" height="30" align="center" grow="1">
<text text="URL" margin="0 5 0 0"/>
<text-input align="center" pad="5" grow="1" height="30" color=".3"/>
<text-input pad="5" grow="1" height="30" color=".3"/>
</node>
<node dir="row" height="30" align="center" grow="1">
<node dir="row" height="80" align="center" grow="1">
<text text="Description" margin="0 5 0 0"/>
<text-input align="center" pad="5" grow="1" height="30" color=".3"/>
<text-input text-vertical-align="top" pad="5" grow="1" min-height="80" color=".3" multiline="1"/>
</node>
</node>
</border>
@@ -2009,5 +2012,6 @@ Here's a list of what's available in this release.
</border>
-->
<!--<ref id="dialog-brush-upload"/>-->
</layout>
</layout>
</root>

352
extra/layout.xsd Normal file
View File

@@ -0,0 +1,352 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="layout"
targetNamespace="http://panopainter.com/layout.xsd"
elementFormDefault="qualified"
xmlns="http://panopainter.com/layout.xsd"
xmlns:mstns="http://panopainter.com/layout.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="layout"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="layout">
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs="0">
<xs:element ref="border"/>
<xs:element ref="button-custom"/>
<xs:element ref="popup-menu"/>
</xs:choice>
<xs:element minOccurs="0" ref="canvas"/>
<xs:element minOccurs="0" ref="node"/>
</xs:sequence>
<xs:attribute name="id" use="required" type="xs:NCName"/>
</xs:complexType>
</xs:element>
<xs:element name="popup-menu">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="border"/>
<xs:element ref="button-custom"/>
</xs:choice>
<xs:attribute name="border-color" use="required" type="xs:decimal"/>
<xs:attribute name="color" use="required"/>
<xs:attribute name="dir" use="required" type="xs:NCName"/>
<xs:attribute name="position" use="required"/>
<xs:attribute name="positioning" use="required" type="xs:NCName"/>
<xs:attribute name="thickness" use="required" type="xs:integer"/>
<xs:attribute name="width" use="required" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="canvas">
<xs:complexType>
<xs:attribute name="grow" use="required" type="xs:integer"/>
<xs:attribute name="height" use="required"/>
<xs:attribute name="id" use="required" type="xs:NCName"/>
<xs:attribute name="positioning" use="required" type="xs:NCName"/>
<xs:attribute name="width" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="border">
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs="0">
<xs:element ref="checkbox"/>
<xs:element ref="image"/>
<xs:element ref="image-texture"/>
<xs:element ref="color-quad"/>
</xs:choice>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="border"/>
<xs:element ref="button"/>
<xs:element ref="button-custom"/>
<xs:element ref="combobox"/>
<xs:element ref="node"/>
<xs:element ref="scroll"/>
<xs:element ref="slider-h"/>
<xs:element ref="text"/>
<xs:element ref="text-input"/>
<xs:element ref="colorwheel"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="align" type="xs:NCName"/>
<xs:attribute name="border-color" type="xs:decimal"/>
<xs:attribute name="color"/>
<xs:attribute name="dir" type="xs:NCName"/>
<xs:attribute name="flood-events" type="xs:integer"/>
<xs:attribute name="grow" type="xs:decimal"/>
<xs:attribute name="height"/>
<xs:attribute name="id" type="xs:NCName"/>
<xs:attribute name="justify" type="xs:NCName"/>
<xs:attribute name="margin"/>
<xs:attribute name="max-width" type="xs:integer"/>
<xs:attribute name="min-height" type="xs:integer"/>
<xs:attribute name="min-width" type="xs:integer"/>
<xs:attribute name="mouse-capture" type="xs:boolean"/>
<xs:attribute name="pad"/>
<xs:attribute name="position"/>
<xs:attribute name="positioning" type="xs:NCName"/>
<xs:attribute name="rtl" type="xs:NCName"/>
<xs:attribute name="shrink" type="xs:integer"/>
<xs:attribute name="thickness" type="xs:integer"/>
<xs:attribute name="width"/>
<xs:attribute name="wrap" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="color-quad">
<xs:complexType>
<xs:attribute name="color" use="required"/>
<xs:attribute name="grow" use="required" type="xs:integer"/>
<xs:attribute name="height" use="required"/>
<xs:attribute name="id" use="required" type="xs:NCName"/>
</xs:complexType>
</xs:element>
<xs:element name="colorwheel">
<xs:complexType>
<xs:attribute name="aspect-ratio" use="required" type="xs:integer"/>
<xs:attribute name="id" use="required" type="xs:NCName"/>
<xs:attribute name="width" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="button-custom">
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs="0">
<xs:element ref="checkbox"/>
<xs:element ref="icon"/>
</xs:choice>
<xs:element minOccurs="0" ref="image"/>
<xs:element minOccurs="0" ref="border"/>
<xs:element minOccurs="0" ref="stroke-preview"/>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="text"/>
<xs:element minOccurs="0" ref="combobox"/>
</xs:sequence>
<xs:attribute name="align" type="xs:NCName"/>
<xs:attribute name="border-color"/>
<xs:attribute name="color"/>
<xs:attribute name="dir" type="xs:NCName"/>
<xs:attribute name="grow" type="xs:integer"/>
<xs:attribute name="height"/>
<xs:attribute name="id" type="xs:NCName"/>
<xs:attribute name="justify" type="xs:NCName"/>
<xs:attribute name="margin"/>
<xs:attribute name="os"/>
<xs:attribute name="pad"/>
<xs:attribute name="shrink" type="xs:integer"/>
<xs:attribute name="thickness" type="xs:integer"/>
<xs:attribute name="width"/>
</xs:complexType>
</xs:element>
<xs:element name="icon">
<xs:complexType>
<xs:attribute name="icon" use="required" type="xs:NCName"/>
<xs:attribute name="width" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="stroke-preview">
<xs:complexType>
<xs:attribute name="grow" type="xs:integer"/>
<xs:attribute name="height" use="required"/>
<xs:attribute name="id" type="xs:NCName"/>
<xs:attribute name="width" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="node">
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="border"/>
<xs:element ref="button-custom"/>
<xs:element ref="checkbox"/>
<xs:element ref="image"/>
<xs:element ref="image-texture"/>
<xs:element ref="node"/>
<xs:element ref="scroll"/>
<xs:element ref="slider-h"/>
<xs:element ref="text"/>
<xs:element ref="panel-quick"/>
</xs:choice>
<xs:choice>
<xs:element ref="combobox"/>
<xs:element ref="text-input"/>
<xs:element ref="slider-hue"/>
<xs:element ref="slider-v"/>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="button"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="align" type="xs:NCName"/>
<xs:attribute name="color"/>
<xs:attribute name="dir" type="xs:NCName"/>
<xs:attribute name="flood-events" type="xs:integer"/>
<xs:attribute name="grow" type="xs:integer"/>
<xs:attribute name="height"/>
<xs:attribute name="id" type="xs:NCName"/>
<xs:attribute name="justify" type="xs:NCName"/>
<xs:attribute name="margin"/>
<xs:attribute name="max-height" type="xs:integer"/>
<xs:attribute name="min-height" type="xs:integer"/>
<xs:attribute name="min-width" type="xs:integer"/>
<xs:attribute name="os"/>
<xs:attribute name="pad"/>
<xs:attribute name="rtl" type="xs:NCName"/>
<xs:attribute name="shrink" type="xs:integer"/>
<xs:attribute name="width"/>
<xs:attribute name="wrap" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="panel-quick">
<xs:complexType>
<xs:attribute name="id" use="required" type="xs:NCName"/>
</xs:complexType>
</xs:element>
<xs:element name="slider-hue">
<xs:complexType>
<xs:attribute name="id" use="required" type="xs:NCName"/>
</xs:complexType>
</xs:element>
<xs:element name="slider-v">
<xs:complexType>
<xs:attribute name="height" use="required" type="xs:integer"/>
<xs:attribute name="id" use="required" type="xs:NCName"/>
<xs:attribute name="shrink" use="required" type="xs:integer"/>
<xs:attribute name="width" use="required" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="image">
<xs:complexType>
<xs:attribute name="align" type="xs:NCName"/>
<xs:attribute name="autosize" type="xs:integer"/>
<xs:attribute name="height"/>
<xs:attribute name="id" type="xs:NCName"/>
<xs:attribute name="justify" type="xs:NCName"/>
<xs:attribute name="mips" type="xs:boolean"/>
<xs:attribute name="path"/>
<xs:attribute name="width" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="checkbox">
<xs:complexType>
<xs:attribute name="height" type="xs:integer"/>
<xs:attribute name="icon"/>
<xs:attribute name="id" use="required" type="xs:NCName"/>
<xs:attribute name="margin"/>
<xs:attribute name="width" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="image-texture">
<xs:complexType>
<xs:choice minOccurs="0">
<xs:element ref="border"/>
<xs:element ref="text"/>
</xs:choice>
<xs:attribute name="aspect-ratio" type="xs:integer"/>
<xs:attribute name="grow" type="xs:integer"/>
<xs:attribute name="height" use="required"/>
<xs:attribute name="id" use="required" type="xs:NCName"/>
<xs:attribute name="pad" type="xs:integer"/>
<xs:attribute name="width" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="text">
<xs:complexType>
<xs:attribute name="align" type="xs:NCName"/>
<xs:attribute name="color"/>
<xs:attribute name="font-size" type="xs:integer"/>
<xs:attribute name="grow" type="xs:integer"/>
<xs:attribute name="height" type="xs:integer"/>
<xs:attribute name="id" type="xs:NCName"/>
<xs:attribute name="justify" type="xs:NCName"/>
<xs:attribute name="margin"/>
<xs:attribute name="os"/>
<xs:attribute name="pad" type="xs:integer"/>
<xs:attribute name="text"/>
<xs:attribute name="text-wrap-width" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="button">
<xs:complexType>
<xs:attribute name="color"/>
<xs:attribute name="grow" type="xs:integer"/>
<xs:attribute name="height" use="required"/>
<xs:attribute name="id" type="xs:NCName"/>
<xs:attribute name="margin"/>
<xs:attribute name="os"/>
<xs:attribute name="pad" type="xs:integer"/>
<xs:attribute name="text" use="required"/>
<xs:attribute name="width" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="scroll">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" ref="ref"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="border"/>
<xs:element ref="image"/>
<xs:element ref="node"/>
</xs:choice>
<xs:choice>
<xs:element ref="button"/>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="text"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="color" use="required"/>
<xs:attribute name="dir" type="xs:NCName"/>
<xs:attribute name="flood-events" type="xs:integer"/>
<xs:attribute name="grow" type="xs:integer"/>
<xs:attribute name="height"/>
<xs:attribute name="id" type="xs:NCName"/>
<xs:attribute name="justify" type="xs:NCName"/>
<xs:attribute name="margin"/>
<xs:attribute name="min-width" type="xs:integer"/>
<xs:attribute name="mouse-capture" type="xs:boolean"/>
<xs:attribute name="pad"/>
<xs:attribute name="rtl" type="xs:NCName"/>
<xs:attribute name="scroll-color" type="xs:decimal"/>
<xs:attribute name="shrink" type="xs:integer"/>
<xs:attribute name="wrap" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="ref">
<xs:complexType>
<xs:attribute name="id" use="required" type="xs:NCName"/>
</xs:complexType>
</xs:element>
<xs:element name="text-input">
<xs:complexType>
<xs:attribute name="align" type="xs:NCName"/>
<xs:attribute name="color" use="required" type="xs:decimal"/>
<xs:attribute name="grow" use="required" type="xs:integer"/>
<xs:attribute name="height" type="xs:integer"/>
<xs:attribute name="id" type="xs:NCName"/>
<xs:attribute name="min-height" type="xs:integer"/>
<xs:attribute name="multiline" type="xs:integer"/>
<xs:attribute name="pad" use="required" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="combobox">
<xs:complexType>
<xs:attribute name="combo-list" use="required"/>
<xs:attribute name="default" type="xs:integer"/>
<xs:attribute name="height" use="required" type="xs:integer"/>
<xs:attribute name="id" use="required" type="xs:NCName"/>
<xs:attribute name="margin"/>
<xs:attribute name="text" type="xs:NMTOKEN"/>
<xs:attribute name="width" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="slider-h">
<xs:complexType>
<xs:attribute name="grow" type="xs:integer"/>
<xs:attribute name="height" type="xs:integer"/>
<xs:attribute name="id" use="required" type="xs:NCName"/>
<xs:attribute name="margin"/>
<xs:attribute name="value" type="xs:decimal"/>
<xs:attribute name="width" type="xs:integer"/>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -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())

View File

@@ -83,7 +83,7 @@ std::vector<TextMesh::Token> 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<glm::vec4> v;
std::vector<GLushort> idx;
glm::vec2 bbmin(FLT_MAX);
glm::vec2 bbmax(-FLT_MAX);
std::vector<Token> parts = max_width > 0 ?
tokenize(text, f) :
std::vector<Token>{ 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)

View File

@@ -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();
};

View File

@@ -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);

View File

@@ -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

View File

@@ -43,5 +43,5 @@ void NodeDialogLayerRename::loaded()
std::string NodeDialogLayerRename::get_name()
{
return input ? input->m_string : "";
return input ? input->m_text : "";
}

View File

@@ -22,7 +22,7 @@ void NodeInputBox::init()
btn_ok = m_template->find<NodeButton>("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<NodeButton>("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;

View File

@@ -14,7 +14,7 @@ void NodeText::clone_copy(Node* dest) const
NodeText* n = static_cast<NodeText*>(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);
}

View File

@@ -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<NodeTextInput*>(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<NodeBorder>();
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()

View File

@@ -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<void(NodeTextInput*target)> 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();
};