TextLine.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. /******************************************************************************/
  5. #define TEXTLINE_OFFSET 0.16f // set >=0.06 (at this value cursor is aligned with the TextLine rect left edge)
  6. #define TEXTLINE_MARGIN 1.0f
  7. #define ADJUST_OFFSET_ON_SEL 0 // if adjust offset when calling select methods
  8. /******************************************************************************/
  9. // MANAGE
  10. /******************************************************************************/
  11. static void Clear(TextLine &tl) {tl.clear();}
  12. void TextLine::zero()
  13. {
  14. kb_lit =true;
  15. show_find=false;
  16. _can_select=false;
  17. _max_length=-1;
  18. _offset = 0;
  19. _func_immediate=false;
  20. _func_user =null;
  21. _func =null;
  22. _edit.reset();
  23. }
  24. TextLine::TextLine() {zero();}
  25. TextLine& TextLine::del()
  26. {
  27. reset. del();
  28. hint. del();
  29. _text. del();
  30. _skin.clear();
  31. super::del(); zero(); return T;
  32. }
  33. void TextLine::createReset()
  34. {
  35. reset.create().func(Clear, T).focusable(false).hide()._parent=this;
  36. reset._sub_type=BUTTON_TYPE_TEXTLINE_CLEAR;
  37. reset.skin=_skin;
  38. }
  39. TextLine& TextLine::create(C Str &text)
  40. {
  41. del();
  42. createReset();
  43. T._type =GO_TEXTLINE;
  44. T._visible =true;
  45. T._rect.max.x= 0.40f;
  46. T._rect.min.y=-0.05f;
  47. return set(text, QUIET);
  48. }
  49. TextLine& TextLine::create(C TextLine &src)
  50. {
  51. if(this!=&src)
  52. {
  53. if(!src.is())del();else
  54. {
  55. copyParams(src);
  56. _type =GO_TEXTLINE;
  57. kb_lit =src. kb_lit;
  58. show_find =src. show_find;
  59. hint =src. hint;
  60. _skin =src._skin;
  61. _can_select =src._can_select;
  62. _max_length =src._max_length;
  63. _offset =src._offset;
  64. _func_immediate=src._func_immediate;
  65. _func_user =src._func_user;
  66. _func =src._func;
  67. _text =src._text;
  68. _edit =src._edit;
  69. reset.create(src.reset)._parent=this;
  70. setTextInput();
  71. }
  72. }
  73. return T;
  74. }
  75. /******************************************************************************/
  76. // GET / SET
  77. /******************************************************************************/
  78. Bool TextLine::showClear ()C {return T().is();}
  79. Flt TextLine::clientWidth()C {Flt w=rect().w(); if(showClear() && reset.visible())w-=reset.rect().w(); return w;}
  80. C Str& TextLine::displayText()C {return password() ? Gui.passTemp(_text.length()) : _text;} // Warning: this is not thread-safe
  81. /******************************************************************************/
  82. void TextLine::setTextInput()C
  83. {
  84. #if ANDROID
  85. if(Gui.kb()==this)Kb.setTextInput(T(), (_edit.sel<0) ? cursor() : _edit.sel, cursor(), password());
  86. #endif
  87. }
  88. /******************************************************************************/
  89. Bool TextLine::password( )C {return _edit.password ; }
  90. TextLine& TextLine::password(Bool on) {if(password()!=on){_edit.password=on; setTextInput();} return T;}
  91. /******************************************************************************/
  92. TextLine& TextLine::maxLength(Int max_length)
  93. {
  94. if( max_length<0)max_length=-1;
  95. if(T._max_length!= max_length)
  96. {
  97. T._max_length=max_length;
  98. if(max_length>=0 && _text.length()>max_length)
  99. {
  100. _text.clip( max_length);
  101. MIN(_edit.cur, max_length);
  102. MIN(_edit.sel, max_length);
  103. setTextInput();
  104. }
  105. }
  106. return T;
  107. }
  108. /******************************************************************************/
  109. void TextLine::adjustOffset()
  110. {
  111. if(GuiSkin *skin=getSkin())
  112. if(TextStyle *text_style=skin->textline.text_style())
  113. {
  114. TextStyleParams ts=*text_style; ts.size=rect().h()*skin->textline.text_size;
  115. #if DEFAULT_FONT_FROM_CUSTOM_SKIN
  116. 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'
  117. #endif
  118. Flt x=ts.textWidth(displayText(), _edit.cur) + _offset + ts.size.x*TEXTLINE_OFFSET, w=clientWidth(), margin=ts.size.x*TEXTLINE_MARGIN;
  119. if( x< margin)_offset =Min(_offset-x+w*0.5f, 0.0f);else
  120. if( x>=w-margin)_offset-= x-w*0.5f;
  121. }
  122. }
  123. Bool TextLine::cursorChanged(Int position)
  124. {
  125. Clamp(position, 0, _text.length()); if(cursor()!=position)
  126. {
  127. _edit.cur=position;
  128. adjustOffset();
  129. return true;
  130. }
  131. return false;
  132. }
  133. TextLine& TextLine::cursor(Int position)
  134. {
  135. if(cursorChanged(position))setTextInput();
  136. return T;
  137. }
  138. /******************************************************************************/
  139. Bool TextLine::setChanged(C Str &text, SET_MODE mode)
  140. {
  141. Str t=text; if(_max_length>=0)t.clip(_max_length);
  142. if(!Equal(T._text, t, true))
  143. {
  144. T._text = t;
  145. T._edit.sel=-1;
  146. if(cursor()>t.length())cursorChanged(t.length());
  147. if(mode!=QUIET)call();
  148. return true;
  149. }
  150. return false;
  151. }
  152. TextLine& TextLine::set(C Str &text, SET_MODE mode)
  153. {
  154. if(setChanged(text, mode))setTextInput();
  155. return T;
  156. }
  157. TextLine& TextLine::clear(SET_MODE mode) {return set(S, mode);}
  158. /******************************************************************************/
  159. TextLine& TextLine::func(void (*func)(Ptr), Ptr user, Bool immediate)
  160. {
  161. T._func =func;
  162. T._func_user =user;
  163. T._func_immediate=immediate;
  164. return T;
  165. }
  166. void TextLine::call()
  167. {
  168. if(_func)if(_func_immediate)_func(_func_user);else Gui.addFuncCall(_func, _func_user);
  169. }
  170. /******************************************************************************/
  171. TextLine& TextLine::selectNone()
  172. {
  173. if(_edit.sel>=0)
  174. {
  175. _edit.sel=-1;
  176. setTextInput();
  177. }
  178. return T;
  179. }
  180. TextLine& TextLine::selectAll()
  181. {
  182. if(_text.is())
  183. if(_edit.sel!=0 || _edit.cur!=_text.length())
  184. {
  185. _edit.sel=0;
  186. #if ADJUST_OFFSET_ON_SEL
  187. cursorChanged(_text.length());
  188. #else
  189. _edit.cur=_text.length();
  190. #endif
  191. setTextInput();
  192. }
  193. return T;
  194. }
  195. TextLine& TextLine::selectExtNot()
  196. {
  197. Int dot=TextPosI(_text, '.');
  198. if(dot<=0)dot=_text.length();
  199. if(dot> 0)
  200. if(_edit.sel!=0 || _edit.cur!=dot)
  201. {
  202. _edit.sel=0;
  203. #if ADJUST_OFFSET_ON_SEL
  204. cursorChanged(dot);
  205. #else
  206. _edit.cur=dot;
  207. #endif
  208. setTextInput();
  209. }
  210. return T;
  211. }
  212. /******************************************************************************/
  213. TextLine& TextLine::rect(C Rect &rect)
  214. {
  215. //if(T.rect()!=rect) below looks fast so don't need this
  216. {
  217. super::rect(rect);
  218. reset .rect(Rect(rect.max.x-rect.h(), rect.min.y, rect.max.x, rect.max.y));
  219. }
  220. return T;
  221. }
  222. TextLine& TextLine::move(C Vec2 &delta)
  223. {
  224. //if(delta.any()) looks fast so skip this check
  225. {
  226. super::move(delta);
  227. reset .move(delta);
  228. }
  229. return T;
  230. }
  231. /******************************************************************************/
  232. TextLine& TextLine::skin(C GuiSkinPtr &skin, Bool sub_objects)
  233. {
  234. T._skin=skin;
  235. if(sub_objects)reset.skin=skin;
  236. return T;
  237. }
  238. /******************************************************************************/
  239. // MAIN
  240. /******************************************************************************/
  241. GuiObj* TextLine::test(C GuiPC &gpc, C Vec2 &pos, GuiObj* &mouse_wheel)
  242. {
  243. if(GuiObj *go=super::test(gpc, pos, mouse_wheel))
  244. {
  245. if(showClear())if(GuiObj *go=reset.test(gpc, pos, mouse_wheel))return go;
  246. return go;
  247. }
  248. return null;
  249. }
  250. void TextLine::update(C GuiPC &gpc)
  251. {
  252. GuiPC gpc2(gpc, visible() && showClear(), enabled());
  253. if( gpc2.enabled)
  254. {
  255. reset.update(gpc2);
  256. if(Gui.kb()==this)
  257. {
  258. #if !ADJUST_OFFSET_ON_SEL
  259. Int sel =_edit.sel;
  260. #endif
  261. Int cur =_edit.cur;
  262. Bool changed= EditText(_text, _edit);
  263. if( changed)
  264. {
  265. if(_max_length>=0 && _text.length()>_max_length)
  266. {
  267. _text.clip( _max_length);
  268. MIN(_edit.cur, _max_length);
  269. MIN(_edit.sel, _max_length);
  270. }
  271. call();
  272. }
  273. if(cur!=_edit.cur || changed
  274. #if !ADJUST_OFFSET_ON_SEL // when offset is not adjusted on selection, then it's possible that cursor is outside of visible space, so when changing selection (clearing it) we should focus on the cursor
  275. || sel!=_edit.sel
  276. #endif
  277. ){adjustOffset(); setTextInput();}
  278. }
  279. C Vec2 *touch_pos =null;
  280. 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;}
  281. if(_text.is() && touch_pos)
  282. {
  283. if(GuiSkin *skin=getSkin())
  284. if(TextStyle *text_style=skin->textline.text_style())
  285. {
  286. TextStyleParams ts=*text_style; ts.size=rect().h()*skin->textline.text_size;
  287. #if DEFAULT_FONT_FROM_CUSTOM_SKIN
  288. 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'
  289. #endif
  290. Int pos=ts.textPos(displayText(), touch_pos->x - rect().min.x - ((Gui._overlay_textline==this) ? Gui._overlay_textline_offset.x : gpc.offset.x) - _offset - ts.size.x*TEXTLINE_OFFSET, !ButtonDb(touch_state) && !_edit.overwrite);
  291. if(ButtonDb(touch_state))
  292. {
  293. _edit.cur=
  294. _edit.sel=pos;
  295. CHAR_TYPE type=CharType(_text[Min(pos, _text.length()-1)]);
  296. for(; _edit.sel && CharType(_text[_edit.sel-1])==type; _edit.sel--);
  297. for(; _edit.cur<_text.length() && CharType(_text[_edit.cur ])==type; _edit.cur++);
  298. if ( _edit.sel==_edit.cur)_edit.sel=-1;
  299. _can_select=false;
  300. setTextInput();
  301. }else
  302. if(_can_select)
  303. {
  304. if(ButtonPd(touch_state))
  305. {
  306. if(_edit.cur!=pos || _edit.sel>=0)
  307. {
  308. _edit.cur=pos;
  309. _edit.sel=-1;
  310. setTextInput();
  311. }
  312. }else
  313. if(pos!=_edit.cur)
  314. {
  315. if(_edit.sel<0)_edit.sel=_edit.cur;
  316. _edit.cur=pos;
  317. setTextInput();
  318. }
  319. // scroll offset
  320. Flt x=ts.textWidth(displayText(), _edit.cur) + _offset + ts.size.x*TEXTLINE_OFFSET, w=clientWidth(), margin=ts.size.x*TEXTLINE_MARGIN;
  321. if( x< margin)_offset =Min(Time.d()*2+_offset, 0.0f);else
  322. if( x>=w-margin)_offset-=Min(Time.d()*2 , x-w*0.5f);
  323. }
  324. }
  325. }else _can_select=true;
  326. }
  327. }
  328. void TextLine::draw(C GuiPC &gpc)
  329. {
  330. if(visible() && gpc.visible)
  331. if(GuiSkin *skin=getSkin())
  332. {
  333. Bool enabled=(T.enabled() && gpc.enabled);
  334. Rect rect=T.rect()+gpc.offset, ext_rect;
  335. C PanelImage *panel_image ; // may be null
  336. C Color *panel_color, *text_color; // never null
  337. if(enabled){panel_image=skin->textline.normal (); panel_color=&skin->textline. normal_panel_color; text_color=&skin->textline. normal_text_color;}
  338. else {panel_image=skin->textline.disabled(); panel_color=&skin->textline.disabled_panel_color; text_color=&skin->textline.disabled_text_color;}
  339. if(panel_image)panel_image->extendedRect(rect, ext_rect);else ext_rect=rect;
  340. if(Cuts(ext_rect, gpc.clip))
  341. {
  342. D.clip(gpc.clip);
  343. if(panel_image )panel_image->draw(*panel_color, TRANSPARENT, rect);else
  344. if(panel_color->a )rect .draw(*panel_color , true );
  345. if(skin->textline.rect_color.a)rect .draw(skin->textline.rect_color, false);
  346. // draw text
  347. if(TextStyle *text_style=skin->textline.text_style())
  348. {
  349. Bool 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
  350. if(T().is() || active || show_find || hint.is())
  351. {
  352. Rect clip_rect=rect; clip_rect.max.x=clip_rect.min.x+clientWidth(); D.clip(clip_rect&gpc.clip);
  353. TextStyleParams ts=*text_style; ts.size=rect.h()*skin->textline.text_size; ts.align.set(1, 0); ts.color=ColorMul(ts.color, *text_color);
  354. #if DEFAULT_FONT_FROM_CUSTOM_SKIN
  355. 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'
  356. #endif
  357. Flt x=rect.min.x + ts.size.x*TEXTLINE_OFFSET, y=rect.centerY();
  358. if(T().is() || active)
  359. {
  360. if(active)ts.edit=&_edit;
  361. D.text(ts, x+_offset, y, displayText());
  362. }else
  363. {
  364. if(show_find)if(Image *image=skin->textline.find_image())
  365. {
  366. x=rect.min.x+image->aspect()*rect.h(); // set new x as the right side of image, so that potential 'hint' will be drawn to the right
  367. image->draw(Rect(rect.min.x, rect.min.y, x, rect.max.y)); // draw on the left
  368. }
  369. if(hint.is())
  370. {
  371. ts.color.a=((ts.color.a*96)>>8); ts.size*=0.85f; D.text(ts, x, y, hint);
  372. }
  373. }
  374. }
  375. }
  376. if(showClear())reset.draw(gpc);
  377. if(kb_lit && Gui.kb()==this){D.clip(gpc.clip); Gui.kbLit(this, rect, skin);}
  378. }
  379. }
  380. }
  381. /******************************************************************************/
  382. // IO
  383. /******************************************************************************/
  384. Bool TextLine::save(File &f, CChar *path)C
  385. {
  386. if(super::save(f, path))
  387. {
  388. f.cmpUIntV(7); // version
  389. f<<kb_lit<<show_find<<_edit.password<<_max_length<<hint<<_text;
  390. f.putBool(reset.visible());
  391. f._putAsset(_skin.name(path));
  392. return f.ok();
  393. }
  394. return false;
  395. }
  396. Bool TextLine::load(File &f, CChar *path)
  397. {
  398. del();
  399. createReset();
  400. if(super::load(f, path))switch(f.decUIntV()) // version
  401. {
  402. case 7:
  403. {
  404. _type=GO_TEXTLINE;
  405. f>>kb_lit>>show_find>>_edit.password>>_max_length>>hint>>_text;
  406. reset.visible(f.getBool());
  407. _skin.require(f._getAsset(), path);
  408. if(f.ok())return true;
  409. }break;
  410. case 6:
  411. {
  412. _type=GO_TEXTLINE;
  413. Bool kb_catch; f>>kb_catch>>kb_lit>>show_find>>_edit.password>>_max_length; f._getStr2(hint)._getStr2(_text);
  414. reset.visible(f.getBool());
  415. _skin.require(f._getAsset(), path);
  416. if(f.ok())return true;
  417. }break;
  418. case 5:
  419. {
  420. _type=GO_TEXTLINE;
  421. f>>kb_lit; f.skip(8); f>>_edit.password>>_max_length; f._getStr2(_text); f._getStr2();
  422. if(f.ok())return true;
  423. }break;
  424. case 4:
  425. {
  426. _type=GO_TEXTLINE;
  427. f>>kb_lit; f.skip(8); f>>_edit.password>>_max_length; f._getStr(_text);
  428. if(f.ok())return true;
  429. }break;
  430. case 3:
  431. {
  432. _type=GO_TEXTLINE;
  433. f>>kb_lit; f.skip(8); f>>_edit.password>>_max_length; f._getStr(_text);
  434. if(f.ok())return true;
  435. }break;
  436. case 2:
  437. {
  438. _type=GO_TEXTLINE;
  439. f>>kb_lit; f.skip(8); f>>_edit.password; f._getStr(_text);
  440. if(f.ok())return true;
  441. }break;
  442. case 1:
  443. {
  444. _type=GO_TEXTLINE;
  445. f>>kb_lit; f.skip(8); f>>_offset; _text=f._getStr16();
  446. if(f.ok())return true;
  447. }break;
  448. case 0:
  449. {
  450. _type=GO_TEXTLINE;
  451. f>>kb_lit; f.skip(8); f>>_offset; _text=f._getStr8();
  452. if(f.ok())return true;
  453. }break;
  454. }
  455. del(); return false;
  456. }
  457. /******************************************************************************/
  458. }
  459. /******************************************************************************/