TextBox.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. /******************************************************************************/
  5. #define TEXTBOX_OFFSET 0.16f // set >=0.06 (at this value cursor is aligned with the TextBox rect left edge) this value is applied to the left and right of the text to add some margin
  6. #define TEXTBOX_MARGIN 2.5f
  7. /******************************************************************************/
  8. void TextBox::zero()
  9. {
  10. kb_lit=true;
  11. _slidebar_size=0.05f;
  12. _can_select=false;
  13. _word_wrap =true;
  14. _max_length=-1;
  15. _func_immediate=false;
  16. _func_user =null;
  17. _func =null;
  18. _edit.reset();
  19. _crect.zero();
  20. }
  21. TextBox::TextBox() {zero();}
  22. TextBox& TextBox::del()
  23. {
  24. slidebar[0].del ();
  25. slidebar[1].del ();
  26. view .del ();
  27. hint .del ();
  28. _text .del ();
  29. _skin .clear();
  30. super::del(); zero(); return T;
  31. }
  32. void TextBox::setParent()
  33. {
  34. slidebar[0]._parent=slidebar[1]._parent=view._parent=this;
  35. }
  36. void TextBox::setParams()
  37. {
  38. _type=GO_TEXTBOX;
  39. view._sub_type=BUTTON_TYPE_REGION_VIEW;
  40. setParent();
  41. }
  42. TextBox& TextBox::create(C Str &text)
  43. {
  44. del();
  45. _visible =true;
  46. _rect.max.x= 0.3f;
  47. _rect.min.y=-0.3f;
  48. view .create().hide().mode=BUTTON_CONTINUOUS;
  49. slidebar[0].create().setLengths(0, 0).hide();
  50. slidebar[1].create().setLengths(0, 0).hide();
  51. view._focusable=slidebar[0]._focusable=slidebar[1]._focusable=false;
  52. setParams();
  53. return set(text, QUIET);
  54. }
  55. TextBox& TextBox::create(C TextBox &src)
  56. {
  57. if(this!=&src)
  58. {
  59. if(!src.is())del();else
  60. {
  61. copyParams(src);
  62. _type =GO_TEXTBOX;
  63. kb_lit =src. kb_lit;
  64. hint =src. hint;
  65. _slidebar_size =src._slidebar_size;
  66. _can_select =src._can_select;
  67. _word_wrap =src._word_wrap;
  68. _max_length =src._max_length;
  69. _func_immediate=src._func_immediate;
  70. _func_user =src._func_user;
  71. _func =src._func;
  72. _crect =src._crect;
  73. _skin =src._skin;
  74. _text =src._text;
  75. _edit =src._edit;
  76. view .create(src.view );
  77. slidebar[0].create(src.slidebar[0]);
  78. slidebar[1].create(src.slidebar[1]);
  79. setParent();
  80. setTextInput();
  81. }
  82. }
  83. return T;
  84. }
  85. /******************************************************************************/
  86. void TextBox::setTextInput()C
  87. {
  88. #if ANDROID
  89. if(Gui.kb()==this)Kb.setTextInput(T(), (_edit.sel<0) ? cursor() : _edit.sel, cursor(), false);
  90. #endif
  91. }
  92. TextBox& TextBox::slidebarSize(Flt size)
  93. {
  94. MAX(size, 0);
  95. if(_slidebar_size!=size)
  96. {
  97. _slidebar_size=size;
  98. if(wordWrap())setVirtualSize();else setButtons(); // in 'wordWrap' mode, virtual size is dependent on the slidebar size
  99. }
  100. return T;
  101. }
  102. void TextBox::setVirtualSize()
  103. {
  104. Vec2 size=0;
  105. if(GuiSkin *skin=getSkin())
  106. if(TextStyle *text_style=skin->textline.text_style())
  107. {
  108. #if DEFAULT_FONT_FROM_CUSTOM_SKIN
  109. TextStyleParams ts=*text_style; if(!ts.font())ts.font(skin->font()); // adjust font in case it's empty and the custom skin has a different font than the 'Gui.skin'
  110. #else
  111. C TextStyle &ts=*text_style;
  112. #endif
  113. Flt offset2=ts.size.x*(TEXTBOX_OFFSET*2), w=rect().w()-offset2; // decrease available width for text by offset for both sides
  114. Int lines =ts.textLines(T(), w, wordWrap() ? AUTO_LINE_SPACE_SPLIT : AUTO_LINE_NONE, &size.x);
  115. size.y=lines*ts.lineHeight();
  116. if(wordWrap()) // check if in word wrap we're exceeding lines beyond client rectangle, in that case slidebar has to be made visible, which reduces client width
  117. {
  118. Flt client_height=rect().h(); // here can't use 'clientHeight' because it may not be available yet
  119. if(size.y>client_height+EPS) // exceeds client height
  120. { // recalculate using smaller width
  121. Int lines=ts.textLines(T(), w-slidebarSize(), /*wordWrap() ? */AUTO_LINE_SPACE_SPLIT/* : AUTO_LINE_NONE*/, &size.x);
  122. size.y=lines*ts.lineHeight();
  123. }
  124. }
  125. size.x+=offset2; // we've calculated text widths with offset removed, but here we're calculating virtual size, and it needs to include it
  126. }
  127. slidebar[0]._length_total=size.x;
  128. slidebar[1]._length_total=size.y;
  129. setButtons();
  130. }
  131. /******************************************************************************/
  132. TextBox& TextBox::maxLength(Int max_length)
  133. {
  134. if( max_length<0)max_length=-1;
  135. if(T._max_length!= max_length)
  136. {
  137. T._max_length=max_length;
  138. if(max_length>=0 && _text.length()>max_length)
  139. {
  140. _text.clip( max_length);
  141. MIN(_edit.cur, max_length);
  142. MIN(_edit.sel, max_length);
  143. setVirtualSize();
  144. setTextInput();
  145. }
  146. }
  147. return T;
  148. }
  149. /******************************************************************************/
  150. Bool TextBox::cursorChanged(Int position)
  151. {
  152. Clamp(position, 0, _text.length());
  153. if(cursor()!=position)
  154. {
  155. _edit.cur=position;
  156. return true;
  157. }
  158. return false;
  159. }
  160. TextBox& TextBox::cursor(Int position)
  161. {
  162. if(cursorChanged(position))
  163. {
  164. setTextInput();
  165. // make cursor visible
  166. if(GuiSkin *skin=getSkin())
  167. if(TextStyle *text_style=skin->textline.text_style())
  168. {
  169. #if DEFAULT_FONT_FROM_CUSTOM_SKIN
  170. TextStyleParams ts=*text_style; if(!ts.font())ts.font(skin->font()); // adjust font in case it's empty and the custom skin has a different font than the 'Gui.skin'
  171. #else
  172. C TextStyle &ts=*text_style;
  173. #endif
  174. Flt offset=ts.size.x*TEXTBOX_OFFSET,
  175. margin=ts.size.x*TEXTBOX_MARGIN;
  176. Vec2 pos=ts.textIndex(T(), cursor(), virtualWidth() - offset*2, wordWrap() ? AUTO_LINE_SPACE_SPLIT : AUTO_LINE_NONE); // here Y is 0..Inf
  177. pos.x+=offset;
  178. Flt pos_left =pos.x-margin,
  179. pos_right =pos.x+margin,
  180. pos_bottom=pos.y+ts.size.y; // bottom position of the cursor (add because Y is inverted)
  181. if(pos_left<slidebar[0].offset() || pos_right >clientWidth ()+slidebar[0].offset())scrollFitX(pos_left, pos_right , true);
  182. if(pos.y <slidebar[1].offset() || pos_bottom>clientHeight()+slidebar[1].offset())scrollFitY(pos.y , pos_bottom, true);
  183. }
  184. }
  185. return T;
  186. }
  187. /******************************************************************************/
  188. Bool TextBox::setChanged(C Str &text, SET_MODE mode)
  189. {
  190. Str t=text; if(_max_length>=0)t.clip(_max_length);
  191. if(!Equal(T._text, t, true))
  192. {
  193. T._text = t;
  194. T._edit.sel=-1;
  195. setVirtualSize();
  196. if(cursor()>t.length())cursorChanged(t.length());
  197. if(mode!=QUIET)call();
  198. return true;
  199. }
  200. return false;
  201. }
  202. TextBox& TextBox::set(C Str &text, SET_MODE mode)
  203. {
  204. if(setChanged(text, mode))setTextInput();
  205. return T;
  206. }
  207. TextBox& TextBox::clear(SET_MODE mode) {return set(S, mode);}
  208. /******************************************************************************/
  209. TextBox& TextBox::func(void (*func)(Ptr), Ptr user, Bool immediate)
  210. {
  211. T._func =func;
  212. T._func_user =user;
  213. T._func_immediate=immediate;
  214. return T;
  215. }
  216. void TextBox::call()
  217. {
  218. if(_func)if(_func_immediate)_func(_func_user);else Gui.addFuncCall(_func, _func_user);
  219. }
  220. /******************************************************************************/
  221. TextBox& TextBox::selectNone()
  222. {
  223. if(_edit.sel>=0)
  224. {
  225. _edit.sel=-1;
  226. setTextInput();
  227. }
  228. return T;
  229. }
  230. TextBox& TextBox::selectAll()
  231. {
  232. if(_text.is())
  233. if(_edit.sel!=0 || _edit.cur!=_text.length())
  234. {
  235. _edit.sel=0;
  236. _edit.cur=_text.length();
  237. setTextInput();
  238. }
  239. return T;
  240. }
  241. /******************************************************************************/
  242. void TextBox::setButtons()
  243. {
  244. Bool vertical, horizontal;
  245. Flt width =virtualWidth (),
  246. height=virtualHeight();
  247. Rect srect=_crect=rect();
  248. if( vertical =(slidebar[1].is() && height>clientHeight()+EPS)){srect.max.x-=slidebarSize(); _crect.max.x-=slidebarSize();}
  249. if( horizontal=(slidebar[0].is() && width >clientWidth ()+EPS)){srect.min.y+=slidebarSize(); if(!wordWrap())_crect.min.y+=slidebarSize();
  250. if(!vertical && slidebar[1].is() && height>clientHeight()+EPS ){srect.max.x-=slidebarSize(); _crect.max.x-=slidebarSize(); vertical=true;}}
  251. slidebar[0].setLengths(clientWidth (), width ).rect(Rect(rect().min.x , rect().min.y, srect .max.x, rect().min.y+slidebarSize())); slidebar[0].visible(slidebar[0]._usable && !wordWrap());
  252. slidebar[1].setLengths(clientHeight(), height).rect(Rect(rect().max.x-slidebarSize(), srect .min.y, rect().max.x, rect().max.y )); slidebar[1].visible(slidebar[1]._usable );
  253. view .rect(Rect(rect().max.x-slidebarSize(), rect().min.y, rect().max.x, rect().min.y+slidebarSize())).visible(slidebar[0]._usable && slidebar[1]._usable);
  254. }
  255. TextBox& TextBox::wordWrap(Bool wrap)
  256. {
  257. if(_word_wrap!=wrap)
  258. {
  259. _word_wrap=wrap;
  260. setVirtualSize();
  261. }
  262. return T;
  263. }
  264. /******************************************************************************/
  265. TextBox& TextBox::rect(C Rect &rect)
  266. {
  267. if(T.rect()!=rect)
  268. {
  269. super::rect(rect);
  270. if(wordWrap())setVirtualSize();else setButtons(); // in 'wordWrap' mode, virtual size is dependent on the rectangle
  271. }
  272. return T;
  273. }
  274. TextBox& TextBox::move(C Vec2 &delta)
  275. {
  276. if(delta.any())
  277. {
  278. super::move(delta);
  279. _crect += delta ;
  280. view .move(delta);
  281. slidebar[0].move(delta);
  282. slidebar[1].move(delta);
  283. }
  284. return T;
  285. }
  286. /******************************************************************************/
  287. TextBox& TextBox::skin(C GuiSkinPtr &skin, Bool sub_objects)
  288. {
  289. if(_skin!=skin)
  290. {
  291. _skin=skin;
  292. if(sub_objects)
  293. {
  294. view .skin=skin ;
  295. REPAO(slidebar).skin(skin);
  296. }
  297. setVirtualSize(); // new skin can have different text style, so have to recalculate
  298. }
  299. return T;
  300. }
  301. /******************************************************************************/
  302. GuiObj* TextBox::test(C GuiPC &gpc, C Vec2 &pos, GuiObj* &mouse_wheel)
  303. {
  304. if(GuiObj *go=super::test(gpc, pos, mouse_wheel))
  305. {
  306. Bool priority=!Kb.shift(); // when 'Kb.shift' disabled (default) then priority=1 (vertical), when enabled then priority=0 (horizontal)
  307. if(slidebar[ priority]._usable)mouse_wheel=&slidebar[ priority];else // check priority slidebar first
  308. if(slidebar[!priority]._usable)mouse_wheel=&slidebar[!priority]; // check !priority slidebar next
  309. GuiPC gpc2(gpc, visible(), enabled());
  310. if(GuiObj *go=slidebar[0].test(gpc2, pos, mouse_wheel))return go;
  311. if(GuiObj *go=slidebar[1].test(gpc2, pos, mouse_wheel))return go;
  312. if(GuiObj *go=view .test(gpc2, pos, mouse_wheel))return go;
  313. return go;
  314. }
  315. return null;
  316. }
  317. /******************************************************************************/
  318. void TextBox::moveCursor(Int lines, Int pages)
  319. {
  320. if(GuiSkin *skin=getSkin())
  321. if(TextStyle *text_style=skin->textline.text_style())
  322. {
  323. #if DEFAULT_FONT_FROM_CUSTOM_SKIN
  324. TextStyleParams ts=*text_style; if(!ts.font())ts.font(skin->font()); // adjust font in case it's empty and the custom skin has a different font than the 'Gui.skin'
  325. #else
  326. C TextStyle &ts=*text_style;
  327. #endif
  328. Flt offset2=ts.size.x*(TEXTBOX_OFFSET*2), width=virtualWidth() - offset2;
  329. AUTO_LINE_MODE auto_line=(wordWrap() ? AUTO_LINE_SPACE_SPLIT : AUTO_LINE_NONE);
  330. if(!Kb.k.shift())_edit.sel=-1;else if(_edit.sel<0)_edit.sel=_edit.cur;
  331. if(pages)
  332. {
  333. Int page_lines=Trunc(clientHeight()/ts.lineHeight());
  334. lines+=pages*Max(1, page_lines); // always move at least one line
  335. }
  336. Vec2 pos=ts.textIndex(T(), cursor(), width, auto_line);
  337. pos.y+=ts.lineHeight()*(lines+0.5f); // add 0.5 to get rounding
  338. Bool eol; _edit.cur=ts.textPos(T(), pos.x, pos.y, true, width, auto_line, eol);
  339. }
  340. }
  341. /******************************************************************************/
  342. void TextBox::update(C GuiPC &gpc)
  343. {
  344. GuiPC gpc2(gpc, visible(), enabled());
  345. if( gpc2.enabled)
  346. {
  347. view.update(gpc2);
  348. if(view())
  349. {
  350. if(Gui.ms()==&view)
  351. {
  352. Ms.freeze();
  353. slidebar[0].setOffset(slidebar[0]._offset + Ms.d().x*2);
  354. slidebar[1].setOffset(slidebar[1]._offset - Ms.d().y*2);
  355. }
  356. REPA(Touches)
  357. {
  358. Touch &t=Touches[i]; if(t.guiObj()==&view && t.on())
  359. {
  360. slidebar[0].setOffset(slidebar[0]._offset + t.d().x*2);
  361. slidebar[1].setOffset(slidebar[1]._offset - t.d().y*2);
  362. }
  363. }
  364. }
  365. // scroll horizontally
  366. if(Ms.wheelX()
  367. && (Gui.wheel()==&slidebar[0] || Gui.wheel()==&slidebar[1]) // if has focus on any of slidebars
  368. && slidebar[0]._usable) // we will scroll only horizontally, so check if that's possible
  369. slidebar[0].scroll(Ms.wheelX()*(slidebar[0]._scroll_mul*slidebar[0].length()+slidebar[0]._scroll_add), slidebar[0]._scroll_immediate);
  370. slidebar[0].update(gpc2);
  371. slidebar[1].update(gpc2);
  372. // update text here
  373. if(Gui.kb()==this)
  374. {
  375. Int cur =_edit.cur;
  376. Bool changed= EditText(_text, _edit, true);
  377. if( changed)
  378. {
  379. if(_max_length>=0 && _text.length()>_max_length)
  380. {
  381. _text.clip( _max_length);
  382. MIN(_edit.cur, _max_length);
  383. MIN(_edit.sel, _max_length);
  384. }
  385. setVirtualSize();
  386. call();
  387. }
  388. if(Kb.k(KB_UP )){moveCursor(-1, 0); Kb.eatKey();}
  389. if(Kb.k(KB_DOWN)){moveCursor( 1, 0); Kb.eatKey();}
  390. if(Kb.k(KB_PGUP)){moveCursor(0, -1); Kb.eatKey();}
  391. if(Kb.k(KB_PGDN)){moveCursor(0, 1); Kb.eatKey();}
  392. if(cur!=_edit.cur || changed){cur=_edit.cur; _edit.cur=-1; cursor(cur);} // set -1 to force adjustment of offset and calling 'setTextInput'
  393. }
  394. C Vec2 *touch_pos =null;
  395. Byte touch_state=0 ; if(Gui.ms()==this && (Ms._button[0]&(BS_ON|BS_PUSHED))){touch_pos=&Ms.pos(); touch_state=Ms._button[0];} if(!touch_pos)REPA(Touches)if(Touches[i].guiObj()==this && (Touches[i]._state&(BS_ON|BS_PUSHED))){touch_pos=&Touches[i].pos(); touch_state=Touches[i]._state;}
  396. if(_text.is() && touch_pos)
  397. {
  398. if(GuiSkin *skin=getSkin())
  399. if(TextStyle *text_style=skin->textline.text_style())
  400. {
  401. #if DEFAULT_FONT_FROM_CUSTOM_SKIN
  402. TextStyleParams ts=*text_style; if(!ts.font())ts.font(skin->font()); // adjust font in case it's empty and the custom skin has a different font than the 'Gui.skin'
  403. #else
  404. C TextStyle &ts=*text_style;
  405. #endif
  406. Flt offset =ts.size.x*TEXTBOX_OFFSET;
  407. Vec2 relative_pos=*touch_pos-gpc.offset,
  408. clipped_pos=relative_pos&_crect; // have to clip so after we start selecting and move mouse outside the client rectangle, we don't set cursor from outside, instead start smooth scrolling when mouse is outside
  409. Bool eol; Int pos=ts.textPos(T(), clipped_pos.x - _crect.min.x + slidebar[0].offset() - offset, _crect.max.y - clipped_pos.y + slidebar[1].offset(), !ButtonDb(touch_state) && !_edit.overwrite, virtualWidth() - offset*2, wordWrap() ? AUTO_LINE_SPACE_SPLIT : AUTO_LINE_NONE, eol);
  410. if(ButtonDb(touch_state))
  411. {
  412. if(eol && pos && _text[pos-1]!='\n')pos--; // if double-clicked at the end of the line and previous character isn't a new line (have to check for this because if we don't then we would go to previous line when clicking on empty lines), then go back, have to check 'eol' and not "_text[pos]=='\n'" because this line could be split because of too long text (in this case there will be 'eol' but no '\n')
  413. Char c=_text[Min(pos, _text.length()-1)];
  414. if(c && c!='\n') // if not end and not new line
  415. {
  416. _edit.cur=_edit.sel=pos;
  417. CHAR_TYPE type=CharType(c);
  418. for(; _edit.sel && CharType(_text[_edit.sel-1])==type; _edit.sel--);
  419. for(; _edit.cur<_text.length() && CharType(_text[_edit.cur ])==type; _edit.cur++);
  420. if ( _edit.sel==_edit.cur)_edit.sel=-1;
  421. }else
  422. {
  423. _edit.cur=pos; _edit.sel=-1; // when double-clicking on an empty line, just leave the cursor in its position
  424. }
  425. _can_select=false;
  426. setTextInput();
  427. }else
  428. if(_can_select)
  429. {
  430. if(ButtonPd(touch_state))
  431. {
  432. if(_edit.cur!=pos || _edit.sel>=0)
  433. {
  434. _edit.cur=_edit.sel=-1; cursor(pos); // set -1 to force adjustment of offset and calling 'setTextInput'
  435. }
  436. }else
  437. if(pos!=_edit.cur)
  438. {
  439. if(_edit.sel<0)_edit.sel=_edit.cur;
  440. _edit.cur=pos;
  441. setTextInput();
  442. }
  443. if(relative_pos.x<_crect.min.x)slidebar[0].button[1].push();else
  444. if(relative_pos.x>_crect.max.x)slidebar[0].button[2].push();
  445. if(relative_pos.y<_crect.min.y)slidebar[1].button[2].push();else
  446. if(relative_pos.y>_crect.max.y)slidebar[1].button[1].push();
  447. }
  448. }
  449. }else _can_select=true;
  450. }
  451. }
  452. void TextBox::draw(C GuiPC &gpc)
  453. {
  454. if(visible() && gpc.visible)
  455. {
  456. GuiSkin *skin=getSkin();
  457. Rect rect=T.rect()+gpc.offset, ext_rect;
  458. if(skin && skin->region.normal)skin->region.normal->extendedRect(rect, ext_rect);else ext_rect=rect;
  459. if(Cuts(ext_rect, gpc.clip))
  460. {
  461. if(skin)
  462. {
  463. D.clip(gpc.clip);
  464. if(skin->region.normal )skin->region.normal->draw(skin->region.normal_color, rect);else
  465. if(skin->region.normal_color.a) rect.draw(skin->region.normal_color);
  466. // text
  467. if(TextStyle *text_style=skin->textline.text_style())
  468. {
  469. Bool enabled=(T.enabled() && gpc.enabled),
  470. active=(Gui.kb()==this && enabled && ((Gui._overlay_textline==this) ? Equal(Gui._overlay_textline_offset, gpc.offset) : true)); // if this is the overlay textline, then draw cursor and editing only if it matches the overlay offset
  471. if(T().is() || active || hint.is())
  472. {
  473. C Color *text_color; // never null
  474. if(enabled)text_color=&skin->textline. normal_text_color;
  475. else text_color=&skin->textline.disabled_text_color;
  476. TextStyleParams ts=*text_style; ts.align.set(1, -1); ts.color=ColorMul(ts.color, *text_color);
  477. #if DEFAULT_FONT_FROM_CUSTOM_SKIN
  478. if(!ts.font())ts.font(skin->font()); // adjust font in case it's empty and the custom skin has a different font than the 'Gui.skin'
  479. #endif
  480. Rect text_rect=_crect+gpc.offset;
  481. D.clip(text_rect&gpc.clip);
  482. Flt offset=TEXTBOX_OFFSET*ts.size.x;
  483. if(T().is() || active)
  484. {
  485. text_rect.min.x+=offset - slidebar[0].offset(); text_rect.max.x=text_rect.min.x+virtualWidth () - offset*2;
  486. text_rect.max.y+= slidebar[1].offset(); text_rect.min.y=text_rect.max.y-virtualHeight() ;
  487. if(active)ts.edit=&_edit;
  488. D.text(ts, text_rect, T(), wordWrap() ? AUTO_LINE_SPACE_SPLIT : AUTO_LINE_NONE);
  489. #if DEBUG && 0 // draw cursor position
  490. Vec2 pos=ts.textIndex(T(), cursor(), text_rect.w(), wordWrap() ? AUTO_LINE_SPACE_SPLIT : AUTO_LINE_NONE);
  491. (pos*Vec2(1, -1) + text_rect.lu()).draw(RED);
  492. #endif
  493. }else
  494. if(hint.is())
  495. {
  496. text_rect.extendX(-offset); // we could've set virtualSize to full client size as a special case in 'setVirtualSize', however to just do this here, is faster (also because most of the time, hint is not displayed)
  497. ts.color.a=((ts.color.a*96)>>8); ts.size*=0.85f; D.text(ts, text_rect, hint, AUTO_LINE_SPACE_SPLIT);
  498. }
  499. }
  500. }
  501. }
  502. view .draw(gpc);
  503. slidebar[0].draw(gpc);
  504. slidebar[1].draw(gpc);
  505. if(kb_lit && Gui.kb()==this){D.clip(gpc.clip); Gui.kbLit(this, rect, skin);}
  506. }
  507. }
  508. }
  509. /******************************************************************************/
  510. Bool TextBox::save(File &f, CChar *path)C
  511. {
  512. if(super::save(f, path))
  513. {
  514. // no need to save '_crect' because we always reset it after loading
  515. f.putMulti(Byte(1), kb_lit, _word_wrap, _max_length, _slidebar_size); // version
  516. f<<hint<<_text;
  517. f.putAsset(_skin.id());
  518. if(view .save(f, path))
  519. if(slidebar[0].save(f, path))
  520. if(slidebar[1].save(f, path))
  521. return f.ok();
  522. }
  523. return false;
  524. }
  525. Bool TextBox::load(File &f, CChar *path)
  526. {
  527. del(); if(super::load(f, path))switch(f.decUIntV()) // version
  528. {
  529. case 1:
  530. {
  531. f.getMulti(kb_lit, _word_wrap, _max_length, _slidebar_size);
  532. f>>hint>>_text;
  533. _skin.require(f.getAssetID(), path);
  534. if(view .load(f, path))
  535. if(slidebar[0].load(f, path))
  536. if(slidebar[1].load(f, path))
  537. if(f.ok()){setParams(); setVirtualSize(); return true;} // have to reset virtual size in case text style has different values now, and because we're not saving '_crect'
  538. }break;
  539. case 0:
  540. {
  541. f.getMulti(kb_lit, _word_wrap, _max_length, _slidebar_size)._getStr2(hint)._getStr2(_text);
  542. _skin.require(f.getAssetID(), path);
  543. if(view .load(f, path))
  544. if(slidebar[0].load(f, path))
  545. if(slidebar[1].load(f, path))
  546. if(f.ok()){setParams(); setVirtualSize(); return true;} // have to reset virtual size in case text style has different values now, and because we're not saving '_crect'
  547. }break;
  548. }
  549. del(); return false;
  550. }
  551. /******************************************************************************/
  552. }
  553. /******************************************************************************/