A different approach which is much better than my previous I think...
This is much easier to setup and use and has a few more features.
With this approach, the only thing needed to create in the GUI editor is a region for the "TextBox". Any region in the GuiOBj that you want to be a TextBox must have "TextBox" in the Description field (it will be removed at runtime).
Two classes are used. One for the TextBoxControl and one for the TextLine.
TextLine class (TextBoxInput)
Code:
class TextBoxInput :TextLine
{
virtual void update(C GuiPC &gpc);
virtual void draw(C GuiPC &gpc);
};
void TextBoxInput.draw(C GuiPC &gpc)
{
// don't draw this element, even if it has focus
}
void TextBoxInput.update(C GuiPC &gpc)
{
super.update(gpc);
// textline doesn't catch new lines, so we'll add them ourselves
if (Kb.bp(KB_ENTER))
{
// insert at cursor position
Str content = T();
content.insert(cursor(), '\n');
T.set(content);
// move cursor behind new line
cursor(cursor() + 1);
}
}
TextBoxControl
Code:
class TextBoxControl : Region
{
// Input callback
static void InputChanged(TextBoxControl& control) { control.OnInputChanged(); }
virtual void update(C GuiPC &gpc);
virtual bool load(File &f, CChar* path=NULL);
Str Get() { return mInput(); }
void Set(Str txt);
void OnInputChanged();
TextBoxControl& func(void (*func)(Ptr user)=NULL, Ptr user=NULL);
T1(TYPE) TextBoxControl& func(void (*func)(TYPE *user), TYPE *user) {return T.func((void(*)(Ptr))func, user);}
T1(TYPE) TextBoxControl& func(void (*func)(TYPE &user), TYPE &user) {return T.func((void(*)(Ptr))func, &user);}
private:
TextBoxInput mInput;
Text mText;
int mCursorCounter;
bool mIsTextBox;
Ptr _func_user;
void (*_func)(Ptr user);
};
/******************************************************************************/
void TextBoxControl.OnInputChanged()
{
// handle TextBoxInput change
// - transfer input text to Text control
// - call user function to signal change (if func defined)
Str content = mInput();
mText.set(content);
if (_func)
_func(_func_user);
}
/******************************************************************************/
TextBoxControl& TextBoxControl.func(void (*func)(Ptr user)=NULL, Ptr user=NULL)
{
if (!mIsTextBox)
return T;
// set user callback
_func = func;
_func_user = user;
return T;
}
/******************************************************************************/
void TextBoxControl.Set(Str txt)
{
if (!mIsTextBox)
return;
// set text for both input and text controls
mInput.set(txt, true);
mText.set(txt);
}
/******************************************************************************/
void TextBoxControl.update(C GuiPC &gpc)
{
super.update(gpc);
if (!mIsTextBox)
return;
// no further update if not focused
if(Gui.kb() != &mInput)
return;
Str content = mInput();
//this shows an symbol at cursor position
if (mCursorCounter < 20)
{
content.insert(mInput.cursor(), '|');
mCursorCounter++;
}
else if (mCursorCounter > 40)
mCursorCounter = 0;
else
mCursorCounter++;
// set the text control contents (with cursor if inserted)
mText.set(content);
// calculate rect size for text control to display scroll bars if necessary
int lines = mText.ts->textLines(content, mText.rect().w(), AUTO_LINE_SPACE);
flt lineHeight = mText.ts.lineHeight();
Flt contentHeight = lineHeight * lines;
flt h = Max(contentHeight -rect().h(), 0.0f);
Rect r;
r.set(0.01f, -rect().h(), rect().w() - 0.06f, 0.0f);
r.extendY(h);
mText.rect(r);
// recalculate slidebar
setSize();
}
/******************************************************************************/
bool TextBoxControl.load(File &f, CChar* path=NULL)
{
if (super.load(f, path))
{
// only setup for textbox behavior if the region is described as "TextBox"
mIsTextBox = false;
if (Equal(desc(), "TextBox"))
{
mIsTextBox = true;
// clear description
desc(S);
Rect r = rect();
// create input
mInput.create(Rect(0.01f, -r.h(), r.w() - 0.06f, 0.0f));
mInput.func(InputChanged, T);
// create text control
mText.create(Rect(0.01f, -r.h(), r.w() - 0.06f, 0.0f));
mText.ts = TextStyles(---some text style UID----);
mText.auto_line = AUTO_LINE_SPACE;
// add as children to this
T += mText;
T += mInput;
mCursorCounter = 0;
_func = 0;
}
return true;
}
return false;
}
/******************************************************************************/
loading GuiObj
Code:
GuiObjs GUI;
GUI.replaceRegion<TextBoxControl>();
GUI.load(..GuiObjUID..);
//////////
// this is optional if you want to handle text change events
TextBoxControl* pInput = (TextBoxControl*)GUI.findRegion(...name of region...);
if(pInput)
pInput->func(..func_name..., T);
/////////////////////
Gui += GUI;
This version is cleaner and requires very little setup. Just create a Region in GUI Editor, set the Description, and replace class before loading the GuiObj. Nothing else is required.
Limitation: I could not properly handle where the cursor is when clicking in the text. This is because the invisible TextLine determines where the cursor is and the scaling between that text and the text control text is not equivalent. I have decided I can live with that and the fact that I cannot handle highlighting selected text. Those things would be nice but I haven't thought of a way to handle it yet.