About Store Forum Documentation Contact



Post Reply 
primitive textbox
Author Message
yvanvds Offline
Member

Post: #1
primitive textbox
I really needed a textbox today, and since it's not in EE yet, I made a small workaround.

I really hope we can discard this thread soon and enjoy a real esenthel TextBox, but here's what i did for now. In short, I just add a new TextLine on top of the text i want to change, but disable its drawroutine. Then i copy the contents of that TextLine to the Text at every update, adding '\n' when enter is pressed. And keep track of the cursor position. The scrollbar still needs work though.

First there's a derived gui object:

PHP Code:
struct hiddenTextLine :TextLine {
    
Text target;
    
Rect targetRect;
    
Str content;

    
void create(Text target);
    
virtual void update    (GuiPC &gpc);
    
virtual void draw        (GuiPC &gpc);
};

void hiddenTextLine::create(Text target) {
    
__super::create();
    
T.target target;
    
targetRect target->rect;
}

void hiddenTextLine::update(GuiPC &gpc) {
    if (
gpc.hidden) return;
    
__super::update(gpc);
    
// copy to string to work on
    
content T();
    
// textline doesn't catch new lines, so we'll add them ourselves
    
if (Kb.bp(KB_ENTER)) {
        
// insert at cursor position
        
content.insert(cursor(), '\n');
        
T.set(content);

        
// move cursor behind new line
        
cursor(cursor() + 1);
    }
    
//this shows an symbol at cursor position
    
content.insert(cursor(), '|');
    
// set this text
    
target->set(content);

    
// calculate text rectangle to adjust region slidebar (this needs more work,
    // but how do we know the height of a line?)
    
Flt h Max(0.9ftarget->tds->textLines(contentrect.w(), AUTO_LINE_SPACE)) * 0.02f;
    
target->rect.set(targetRect.min Vec2(0,h), targetRect.max );
    
rect target->rect;

    
// recalculate slidebar
    
parent()->asRegion().setSize();
}

void hiddenTextLine::draw(GuiPC &gpc) {
    
// don't draw this element, even if it has focus
    //__super::draw(gpc);


And i've implemented it like this. This is part of the create function of a gui window. It contains a region (talkregion) so that you can have a scrollbar next to the text. Then there's a normal Text (text) and the hiddenText (the above hiddenTextLine). hiddenText should have the same dimensions as text, so you can properly select it.

PHP Code:
void dialog::create() {
    if (
obj.load("dialog.gobj")) {
        
window = &obj.getWindow("window");
        
talkRegion = &obj.getRegion("talkregion");
        
text = &obj.getText("text");
        
        
// to simulate textbox
        
hiddenText.create(text);
        
hiddenText.rect text->rect;
        
hiddenText.pos(text->pos());
        (*
talkRegion) += hiddenText;

        
Gui += *window;
    }


I hope this is usefull for others. Or perhaps someone can even improve it :-)
(This post was last modified: 01-15-2012 09:43 PM by yvanvds.)
01-15-2012 09:40 PM
Find all posts by this user Quote this message in a reply
yvanvds Offline
Member

Post: #2
RE: primitive textbox
Thanks! I hope it's not too useful though, or Esenthel might decide to lower the TextBox priority on the roadmap. :-)
01-16-2012 06:30 PM
Find all posts by this user Quote this message in a reply
Esenthel Offline
Administrator

Post: #3
RE: primitive textbox
wink
01-16-2012 07:10 PM
Find all posts by this user Quote this message in a reply
Rofar Offline
Member

Post: #4
RE: primitive textbox
This was really helpful so I will post a slight variation of this for EE 2.0 with using the GUI Editor to create the controls.

The difference between the original code and mine is:
1. up to date with API changes since the original post.
2. requires all controls to be created in the GUI Editor.
3. displays a blinking cursor
4. calculates scroll region size differently and does not auto-break the lines. Enter key must be used to start a new text line.
5. only handles focused controls. (Original code would create new lines for every "Textbox" that exists within the GUI when ENTER is pressed).

TextBoxControl class
Code:
class TextBoxControl :TextLine
{
   void create(Text * target);
   virtual void update(C GuiPC &gpc);
   virtual void draw(C GuiPC &gpc);
   virtual bool load(File &f, CChar* path=NULL);
  
private:

   Text* mTarget;
   Rect mTargetRect;
   Str mContent;
   int mDoCursor;
   int mLineCount;
};

void TextBoxControl.create(Text * target)
{
   mTarget = target;
   mDoCursor = 0;
   mLineCount = 1;
   if(mTarget)
      mTargetRect = mTarget->rect();
}

void TextBoxControl.update(C GuiPC &gpc)
{
   if (gpc.hidden())
      return;

   if (Kb.bp(KB_DEL))
   {
      if (mContent[cursor()] == '\n')
         mLineCount--;
   }
   else if(Kb.bp(KB_BACK))
   {
      if (mContent[cursor()-1] == '\n')
         mLineCount--;
   }
  
   super.update(gpc);
   if (!mTarget)
      return;
  
   if(Gui.kb() != this)
      return;
      
   // copy to string to work on
   mContent = T();
   // textline doesn't catch new lines, so we'll add them ourselves
   if (Kb.bp(KB_ENTER))
   {
      // insert at cursor position
      mContent.insert(cursor(), '\n');
      T.set(mContent);

      // move cursor behind new line
      cursor(cursor() + 1);
      mLineCount++;
   }
  
  
   //this shows an symbol at cursor position
   if (mDoCursor < 20)
   {
      mContent.insert(cursor(), '|');
      mDoCursor++;
   }
   else if (mDoCursor > 40)
      mDoCursor = 0;
   else
      mDoCursor++;
      
   // set this text
   mTarget->set(mContent);

   // calculate text rectangle to adjust region slidebar (this needs more work,
   // but how do we know the height of a line?)
   Flt lineHeight = mTarget->ts.lineHeight();
   Flt contentHeight = lineHeight * mLineCount;
   flt deltaHeight = (Max(contentHeight - mTargetRect.h(), 0.0f));
  
   Rect r;
   r.set(mTargetRect.min, mTargetRect.max);
   r.extendY(deltaHeight);
   mTarget->rect(r);

   // recalculate slidebar
   parent()->asRegion().setSize();
}

void TextBoxControl.draw(C GuiPC &gpc)
{
  
   // don't draw this element, even if it has focus
   if (!mTarget)
      super.draw(gpc);
}
bool TextBoxControl.load(File &f, CChar* path=NULL)
{
   // This is so any TextLine controls in the GuiObj
   // that does not link to a target has a null target.
   bool b = super.load(f, path);
   mTarget = 0;
   return b;
}

loading GuiObj and setting TextBoxControls
Code:
GuiObjs GUI;
GUI.replaceTextLine<TextBoxControl>();
GUI.load(..GuiObjUID..);

TextBoxControl* pInput = (TextBoxControl*)GUI.findTextLine(...name of text line...);
Text* pText = GUI.findText(...name of text control...);
        
if(pInput)
{
   pInput->create(pText);
}
Gui += GUI;

In GUI Editor, this consists of a Region with a Text and a TextLine as children. All 3 controls should be the same size. Making the TextLine and Text controls about 0.02 less in width than the Region has the best results.
08-26-2013 02:01 AM
Find all posts by this user Quote this message in a reply
Pixel Perfect Offline
Member

Post: #5
RE: primitive textbox
Nice update, cheers Rofar smile
08-26-2013 11:21 AM
Find all posts by this user Quote this message in a reply
yvanvds Offline
Member

Post: #6
RE: primitive textbox
Thanks, Rofar! Nice update.
08-26-2013 03:55 PM
Find all posts by this user Quote this message in a reply
Rofar Offline
Member

Post: #7
RE: primitive textbox
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.
(This post was last modified: 08-28-2013 03:14 AM by Rofar.)
08-28-2013 03:01 AM
Find all posts by this user Quote this message in a reply
Post Reply