SimpleGui.Mod 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. MODULE SimpleGui;
  2. IMPORT G := Graph, Strings, Out;
  3. TYPE
  4. Widget* = POINTER TO WidgetDesc;
  5. Message* = RECORD END;
  6. DrawMsg* = RECORD(Message) x*, y*, w*, h*: INTEGER END;
  7. MouseMoveMsg* = RECORD(Message) x*, y*, btn*: INTEGER END;
  8. MouseDownMsg* = RECORD(Message) x*, y*, btn*: INTEGER END;
  9. MouseUpMsg* = RECORD(Message) x*, y*, btn*: INTEGER END;
  10. MouseEnterMsg* = RECORD(Message) END;
  11. MouseLeaveMsg* = RECORD(Message) END;
  12. ClickMsg* = RECORD(Message) END;
  13. GetFocusMsg* = RECORD(Message) END;
  14. LostFocusMsg* = RECORD(Message) END;
  15. KeyDownMsg* = RECORD(Message) key*: INTEGER END;
  16. KeyUpMsg* = RECORD(Message) key*: INTEGER END;
  17. CharMsg* = RECORD(Message)
  18. key*: INTEGER;
  19. ch*: CHAR;
  20. mod*: SET;
  21. repeat*: BOOLEAN
  22. END;
  23. Handler* = PROCEDURE (c: Widget; VAR msg: Message);
  24. WidgetDesc* = RECORD
  25. x*, y*, w*, h*: INTEGER;
  26. bgColor*, fgColor*: G.Color;
  27. focusable*: BOOLEAN; (** TRUE if widget can get focus *)
  28. focused*: BOOLEAN; (** TRUE if this widget is globally in focus *)
  29. hovered*: BOOLEAN; (** TRUE if mouse pointer is over the widget *)
  30. pressed*: BOOLEAN; (** TRUE if widget is held down with LMB *)
  31. body*: Widget; (** A ring of widgets that this widget contains *)
  32. parent*: Widget; (** A widget that this widget is contained in *)
  33. prev*, next*: Widget;
  34. handle*: Handler;
  35. onMouseDown*: PROCEDURE (c: Widget; x, y, btn: INTEGER);
  36. onMouseUp*: PROCEDURE (c: Widget; x, y, btn: INTEGER);
  37. onMouseMove*: PROCEDURE (c: Widget; x, y, btn: INTEGER);
  38. onMouseEnter*: PROCEDURE (c: Widget);
  39. onMouseLeave*: PROCEDURE (c: Widget);
  40. onClick*: PROCEDURE (c: Widget);
  41. onKeyDown*: PROCEDURE (c: Widget; key: INTEGER);
  42. onKeyUp*: PROCEDURE (c: Widget; key: INTEGER);
  43. onChar*: PROCEDURE (c: Widget; key: INTEGER; ch: CHAR; mod: SET; repeat: BOOLEAN);
  44. END;
  45. Panel* = POINTER TO PanelDesc;
  46. PanelDesc* = RECORD(WidgetDesc) END;
  47. App* = POINTER TO AppDesc;
  48. AppDesc* = RECORD(WidgetDesc) END;
  49. Form* = POINTER TO FormDesc;
  50. FormDesc* = RECORD(WidgetDesc) END;
  51. Button* = POINTER TO ButtonDesc;
  52. ButtonDesc* = RECORD(WidgetDesc)
  53. caption*: ARRAY 64 OF CHAR
  54. END;
  55. Edit* = POINTER TO EditDesc;
  56. EditDesc* = RECORD(WidgetDesc)
  57. text*: ARRAY 256 OF CHAR;
  58. len*: INTEGER; (** Length of text in characters *)
  59. pos*: INTEGER; (** Position of text carret, in range [0; len] *)
  60. off*: INTEGER (** Used to slide text that does not fit, normal is 0 *)
  61. END;
  62. ScrollBar* = POINTER TO ScrollBarDesc;
  63. ScrollBarDesc* = RECORD(WidgetDesc)
  64. vertical*: BOOLEAN; (** TRUE for vertical scroll, FALSE for horizontal *)
  65. min*, max*: INTEGER;
  66. value*: INTEGER; (** The position of the scroll, in range [min; max] *)
  67. inc*, bigInc*: INTEGER; (** A single increment of value, and a big one *)
  68. btnSize*: INTEGER; (** Width or height (depending on vertical) of buttons *)
  69. handlePos*, handleSize*: INTEGER; (** Size and position of handle, px *)
  70. handlePressed*: BOOLEAN;
  71. handlePressPos*: INTEGER; (** Where handle was pressed, offset in px *)
  72. btnPressed*: INTEGER; (** 0-nothing, 1-less btn, 2-more btn, 3-handle *)
  73. onScroll*: PROCEDURE (c: Widget; value: INTEGER);
  74. END;
  75. VAR
  76. Done*: BOOLEAN; (** FALSE after a failed opration and before the next Init *)
  77. app*: App;
  78. focusedWidget*: Widget; (** The widget with focus = TRUE *)
  79. font*: G.Font;
  80. quit: BOOLEAN; (** Main loop in procedure Run ends when TRUE *)
  81. hoveredWidget: Widget;
  82. pressedWidget: Widget;
  83. pressedX, pressedY: INTEGER;
  84. (** Widget **)
  85. PROCEDURE FindHoveredInRing(list: Widget; x, y: INTEGER;
  86. forMouseDown: BOOLEAN): Widget;
  87. VAR c: Widget;
  88. BEGIN
  89. IF list # NIL THEN
  90. c := list.prev;
  91. WHILE (c # NIL) &
  92. ~((c.x <= x) & (x < c.x + c.w) &
  93. (c.y <= y) & (y < c.y + c.h))
  94. DO
  95. IF c = list THEN c := NIL ELSE c := c.prev END
  96. END;
  97. IF forMouseDown & (c # NIL) THEN
  98. INC(pressedX, c.x); INC(pressedY, c.y)
  99. END
  100. ELSE c := NIL
  101. END
  102. RETURN c END FindHoveredInRing;
  103. PROCEDURE WidgetOnMouseEnter*(c: Widget);
  104. VAR msg: MouseEnterMsg;
  105. BEGIN
  106. IF pressedWidget = c THEN c.pressed := TRUE END;
  107. c.hovered := TRUE;
  108. c.handle(c, msg)
  109. END WidgetOnMouseEnter;
  110. PROCEDURE WidgetOnMouseLeave*(c: Widget);
  111. VAR msg: MouseLeaveMsg;
  112. BEGIN
  113. c.hovered := FALSE;
  114. c.pressed := FALSE;
  115. c.handle(c, msg)
  116. END WidgetOnMouseLeave;
  117. PROCEDURE WidgetOnMouseMove*(c: Widget; x, y, btn: INTEGER);
  118. VAR msg: MouseMoveMsg;
  119. BEGIN
  120. IF (0 <= x) & (x < c.w) & (0 <= y) & (y < c.h) THEN
  121. IF c # hoveredWidget THEN
  122. IF hoveredWidget # NIL THEN WidgetOnMouseLeave(hoveredWidget) END;
  123. hoveredWidget := c;
  124. WidgetOnMouseEnter(hoveredWidget)
  125. END
  126. ELSIF c = hoveredWidget THEN
  127. WidgetOnMouseLeave(c);
  128. hoveredWidget := NIL
  129. END;
  130. msg.x := x; msg.y := y; msg.btn := btn;
  131. c.handle(c, msg);
  132. IF c.onMouseMove # NIL THEN c.onMouseMove(c, x, y, btn) END
  133. END WidgetOnMouseMove;
  134. PROCEDURE WidgetHandleMouseMove*(c: Widget; x, y, btn: INTEGER);
  135. VAR p: Widget;
  136. BEGIN
  137. IF pressedWidget # NIL THEN
  138. WidgetOnMouseMove(pressedWidget, x - pressedX, y - pressedY, btn)
  139. ELSE
  140. p := FindHoveredInRing(c.body, x, y, FALSE);
  141. IF p # NIL THEN
  142. WidgetHandleMouseMove(p, x - p.x, y - p.y, btn)
  143. ELSE
  144. WidgetOnMouseMove(c, x, y, btn)
  145. END
  146. END
  147. END WidgetHandleMouseMove;
  148. PROCEDURE Focus*(c: Widget);
  149. VAR get: GetFocusMsg;
  150. lost: LostFocusMsg;
  151. BEGIN
  152. IF c.focusable THEN
  153. IF focusedWidget # NIL THEN
  154. focusedWidget.focused := FALSE;
  155. focusedWidget.handle(focusedWidget, lost)
  156. END;
  157. c.focused := TRUE;
  158. focusedWidget := c;
  159. focusedWidget.handle(focusedWidget, get)
  160. END
  161. END Focus;
  162. PROCEDURE WidgetOnMouseDown*(c: Widget; x, y, btn: INTEGER);
  163. VAR msg: MouseDownMsg;
  164. BEGIN
  165. pressedWidget := c;
  166. Focus(c);
  167. msg.x := x; msg.y := y; msg.btn := btn;
  168. c.handle(c, msg);
  169. IF c.onMouseDown # NIL THEN c.onMouseDown(c, x, y, btn) END
  170. END WidgetOnMouseDown;
  171. PROCEDURE WidgetHandleMouseDown*(c: Widget; x, y, btn: INTEGER);
  172. VAR p: Widget;
  173. BEGIN
  174. p := FindHoveredInRing(c.body, x, y, TRUE);
  175. IF p # NIL THEN
  176. WidgetHandleMouseDown(p, x - p.x, y - p.y, btn)
  177. ELSE
  178. WidgetOnMouseDown(c, x, y, btn)
  179. END
  180. END WidgetHandleMouseDown;
  181. PROCEDURE WidgetOnMouseUp*(c: Widget; x, y, btn: INTEGER);
  182. VAR msg: MouseUpMsg;
  183. BEGIN
  184. pressedWidget := NIL;
  185. msg.x := x; msg.y := y; msg.btn := btn;
  186. c.handle(c, msg);
  187. IF c.onMouseUp # NIL THEN c.onMouseUp(c, x, y, btn) END
  188. END WidgetOnMouseUp;
  189. PROCEDURE WidgetOnClick*(c: Widget);
  190. VAR msg: ClickMsg;
  191. BEGIN c.handle(c, msg);
  192. IF c.onClick # NIL THEN c.onClick(c) END
  193. END WidgetOnClick;
  194. PROCEDURE WidgetHandler*(c: Widget; VAR msg: Message);
  195. VAR x, y: INTEGER;
  196. BEGIN
  197. IF msg IS DrawMsg THEN
  198. x := msg(DrawMsg).x; y := msg(DrawMsg).y;
  199. G.FillRect(x, y, x + c.w - 1, y + c.h - 1, c.bgColor);
  200. G.Rect(x, y, x + c.w - 1, y + c.h - 1, c.fgColor);
  201. G.Rect(x + 2, y + 2, x + c.w - 3, y + c.h - 3, c.fgColor)
  202. ELSIF msg IS MouseDownMsg THEN
  203. IF msg(MouseDownMsg).btn = 1 THEN c.pressed := TRUE END
  204. ELSIF msg IS MouseUpMsg THEN c.pressed := FALSE
  205. END
  206. END WidgetHandler;
  207. PROCEDURE DrawWidget*(c: Widget; x, y, w, h: INTEGER);
  208. VAR M: DrawMsg;
  209. BEGIN
  210. M.x := x; M.y := y; M.w := w; M.h := h;
  211. c.handle(c, M)
  212. END DrawWidget;
  213. PROCEDURE DrawBody*(c: Widget; x, y, w, h: INTEGER);
  214. VAR p: Widget;
  215. x2, y2, w2, h2: INTEGER;
  216. cx, cy, cw, ch: INTEGER;
  217. BEGIN
  218. p := c.body;
  219. IF p # NIL THEN
  220. REPEAT
  221. x2 := x + p.x; y2 := y + p.y;
  222. w2 := w - p.x; h2 := h - p.y;
  223. cx := x2; cy := y2; cw := p.w; ch := p.h;
  224. IF cx + cw > x + w THEN cw := x + w - cx END;
  225. IF cy + ch > y + h THEN ch := y + h - cy END;
  226. IF cx < x THEN DEC(cw, x - cx); cx := x END;
  227. IF cy < y THEN DEC(ch, y - cy); cy := y END;
  228. G.SetClip(cx, cy, cw, ch);
  229. DrawWidget(p, x2, y2, p.w, p.h);
  230. p := p.next
  231. UNTIL p = c.body;
  232. G.UnsetClip
  233. END
  234. END DrawBody;
  235. PROCEDURE SetBgColor*(c: Widget; color: G.Color);
  236. BEGIN c.bgColor := color
  237. END SetBgColor;
  238. PROCEDURE SetFgColor*(c: Widget; color: G.Color);
  239. BEGIN c.fgColor := color
  240. END SetFgColor;
  241. PROCEDURE SetOnMouseMove*(c: Widget; proc: PROCEDURE (c: Widget; x, y, btn: INTEGER));
  242. BEGIN c.onMouseMove := proc
  243. END SetOnMouseMove;
  244. PROCEDURE SetOnMouseDown*(c: Widget; proc: PROCEDURE (c: Widget; x, y, btn: INTEGER));
  245. BEGIN c.onMouseDown := proc
  246. END SetOnMouseDown;
  247. PROCEDURE SetOnMouseUp*(c: Widget; proc: PROCEDURE (c: Widget; x, y, btn: INTEGER));
  248. BEGIN c.onMouseUp := proc
  249. END SetOnMouseUp;
  250. PROCEDURE SetOnClick*(c: Widget; proc: PROCEDURE (c: Widget));
  251. BEGIN c.onClick := proc
  252. END SetOnClick;
  253. PROCEDURE InitWidget*(c: Widget; w, h: INTEGER);
  254. BEGIN c.x := 0; c.y := 0; c.w := w; c.h := h;
  255. c.focusable := FALSE; c.focused := FALSE;
  256. c.hovered := FALSE; c.pressed := FALSE;
  257. G.MakeCol(c.bgColor, 180, 180, 180);
  258. G.MakeCol(c.fgColor, 0, 0, 0);
  259. c.handle := WidgetHandler
  260. END InitWidget;
  261. PROCEDURE Detach*(c: Widget);
  262. VAR p: Widget;
  263. BEGIN
  264. IF c.parent # NIL THEN
  265. IF c.prev = c THEN
  266. c.parent.body := NIL
  267. ELSE
  268. c.prev.next := c.next;
  269. c.next.prev := c.prev
  270. END;
  271. c.parent := NIL
  272. END;
  273. c.prev := NIL; c.next := NIL
  274. END Detach;
  275. PROCEDURE AppendTo*(c: Widget; container: Widget);
  276. VAR r: Widget;
  277. BEGIN
  278. Detach(c);
  279. c.parent := container;
  280. r := container.body;
  281. IF r = NIL THEN
  282. container.body := c;
  283. c.prev := c; c.next := c
  284. ELSE
  285. c.next := r; c.prev := r.prev;
  286. r.prev.next := c; r.prev := c
  287. END
  288. END AppendTo;
  289. PROCEDURE Put*(c, where: Widget; x, y: INTEGER);
  290. VAR p: Widget;
  291. BEGIN
  292. IF (c # NIL) & (where # NIL) THEN
  293. c.x := x; c.y := y;
  294. AppendTo(c, where)
  295. END
  296. END Put;
  297. (** Panel **)
  298. PROCEDURE PanelHandler*(c: Widget; VAR msg: Message);
  299. VAR x, y: INTEGER;
  300. BEGIN
  301. IF msg IS DrawMsg THEN
  302. x := msg(DrawMsg).x; y := msg(DrawMsg).y;
  303. G.FillRect(x, y, x + c.w - 1, y + c.h - 1, c.bgColor);
  304. DrawBody(c, x, y, c.w, c.h)
  305. ELSE WidgetHandler(c, msg)
  306. END
  307. END PanelHandler;
  308. PROCEDURE InitPanel*(c: Panel; where: Widget; x, y, w, h: INTEGER);
  309. BEGIN InitWidget(c, w, h);
  310. c.handle := PanelHandler;
  311. Put(c, where, x, y)
  312. END InitPanel;
  313. PROCEDURE NewPanel*(where: Widget; x, y, w, h: INTEGER): Panel;
  314. VAR c: Panel;
  315. BEGIN NEW(c); InitPanel(c, where, x, y, w, h)
  316. RETURN c END NewPanel;
  317. (** App **)
  318. PROCEDURE InitApp*(c: App);
  319. VAR W, H: INTEGER;
  320. BEGIN
  321. G.GetScreenSize(W, H);
  322. InitWidget(c, W, H)
  323. END InitApp;
  324. PROCEDURE NewApp*(): App;
  325. VAR c: App;
  326. BEGIN NEW(c); InitApp(c)
  327. RETURN c END NewApp;
  328. (** Form **)
  329. PROCEDURE DrawForm*(c: Form);
  330. BEGIN
  331. G.FillRect(c.x, c.y, c.x + c.w - 1, c.y + c.h - 1, c.bgColor);
  332. DrawBody(c, c.x, c.y, c.w, c.h)
  333. END DrawForm;
  334. PROCEDURE FormHandler*(c: Widget; VAR msg: Message);
  335. BEGIN WidgetHandler(c, msg)
  336. END FormHandler;
  337. PROCEDURE InitForm*(c: Form; x, y, w, h: INTEGER);
  338. BEGIN InitWidget(c, w, h);
  339. c.x := x; c.y := y;
  340. c.handle := FormHandler;
  341. AppendTo(c, app)
  342. END InitForm;
  343. PROCEDURE NewForm*(x, y, w, h: INTEGER): Form;
  344. VAR c: Form;
  345. BEGIN NEW(c); InitForm(c, x, y, w, h)
  346. RETURN c END NewForm;
  347. (** Button **)
  348. PROCEDURE MakeOrAndYw(bg: G.Color; VAR or, yw: G.Color);
  349. VAR r, g, b: INTEGER;
  350. BEGIN
  351. G.ColorToRGB(bg, r, g, b);
  352. G.MakeCol(yw, (r + 255 * 2) DIV 3, (g + 255 * 3) DIV 4, (b * 3 + 255) DIV 4);
  353. IF (r <= g) & (r <= b) THEN
  354. g := (g * 2 + 255 * 3) DIV 5;
  355. b := (b * 3 + 255) DIV 4
  356. ELSIF (g <= r) & (g <= b) THEN
  357. r := (r * 2 + 255 * 3) DIV 5;
  358. b := (b * 3 + 255) DIV 4
  359. ELSE
  360. r := (r * 2 + 255 * 3) DIV 5;
  361. g := (g * 3 + 255) DIV 4
  362. END;
  363. G.MakeCol(or, r, g, b)
  364. END MakeOrAndYw;
  365. PROCEDURE DrawButtonBox(x, y, w, h: INTEGER; bg, parentBg: G.Color;
  366. down, glow: BOOLEAN);
  367. VAR wh, bl, g1, g2, or, yw: G.Color;
  368. X, Y: INTEGER;
  369. BEGIN
  370. G.MakeCol(bl, 0, 0, 0);
  371. G.MakeCol(wh, 255, 255, 255);
  372. G.MakeCol(g1, 140, 140, 140);
  373. G.MakeCol(g2, 80, 80, 80);
  374. MakeOrAndYw(parentBg, or, yw);
  375. X := x + w - 1; Y := y + h - 1;
  376. G.FillRect(x + 1, y + 1, X - 2, Y - 2, bg);
  377. G.HLine(x + 2, y, X - 1, bl);
  378. G.HLine(x, Y - 1, X - 4, bl);
  379. G.VLine(x, y + 2, Y - 1, bl);
  380. G.VLine(X - 1, y + 1, Y - 4, bl);
  381. IF ~down THEN
  382. G.HLine(x + 3, y + 1, X - 2, wh);
  383. G.HLine(x + 2, Y - 2, X - 4, g1);
  384. G.VLine(x + 1, y + 3, Y - 2, wh);
  385. G.VLine(X - 2, y + 2, Y - 4, g1);
  386. G.PutPixel(X - 3, Y - 3, g1);
  387. G.Line(X - 4, Y - 3, X - 3, Y - 4, g1);
  388. G.PutPixel(x + 2, y + 2, wh)
  389. END;
  390. G.Line(X - 3, Y - 2, X - 2, Y - 3, bl);
  391. G.Line(X - 3, Y - 1, X - 1, Y - 3, g2);
  392. G.Line(x + 1, y + 2, x + 2, y + 1, g1);
  393. G.PutPixel(x + 1, y + 1, bl);
  394. IF glow THEN
  395. G.Line(X - 2, Y - 1, X - 1, Y - 2, yw);
  396. G.HLine(x + 1, Y, X - 2, or);
  397. G.VLine(X, y + 1, Y - 2, or);
  398. G.PutPixel(X - 1, Y - 1, or)
  399. END
  400. END DrawButtonBox;
  401. PROCEDURE DrawButton*(c: Button; x, y, w, h: INTEGER);
  402. VAR fw, fh, tw, tx, ty: INTEGER;
  403. BEGIN
  404. DrawButtonBox(x, y, c.w, c.h, c.bgColor, c.parent.bgColor,
  405. c.pressed & c.hovered, TRUE);
  406. G.GetMonoFontSize(font, fw, fh);
  407. tw := Strings.Length(c.caption) * fw;
  408. tx := x + (c.w - tw) DIV 2;
  409. ty := y + (c.h - fh) DIV 2;
  410. IF c.pressed & c.hovered THEN INC(tx); INC(ty) END;
  411. G.DrawString(c.caption, tx, ty, font, c.fgColor)
  412. END DrawButton;
  413. PROCEDURE ButtonHandler*(c: Widget; VAR msg: Message);
  414. VAR b: Button;
  415. BEGIN b := c(Button);
  416. IF msg IS DrawMsg THEN
  417. DrawButton(b, msg(DrawMsg).x, msg(DrawMsg).y,
  418. msg(DrawMsg).w, msg(DrawMsg).h)
  419. ELSE WidgetHandler(c, msg)
  420. END
  421. END ButtonHandler;
  422. PROCEDURE InitButton*(c: Button; where: Widget;
  423. x, y, w, h: INTEGER; caption: ARRAY OF CHAR);
  424. BEGIN InitWidget(c, w, h);
  425. Strings.Copy(caption, c.caption);
  426. c.focusable := TRUE;
  427. c.handle := ButtonHandler;
  428. Put(c, where, x, y)
  429. END InitButton;
  430. PROCEDURE NewButton*(where: Widget; x, y, w, h: INTEGER; caption: ARRAY OF CHAR): Button;
  431. VAR c: Button;
  432. BEGIN NEW(c); InitButton(c, where, x, y, w, h, caption)
  433. RETURN c END NewButton;
  434. (** Edit **)
  435. PROCEDURE DrawEdit*(c: Edit; x, y, w, h: INTEGER);
  436. VAR fw, fh, tw, tx, ty: INTEGER;
  437. or, yw: G.Color;
  438. BEGIN
  439. MakeOrAndYw(c.parent.bgColor, or, yw);
  440. G.FillRect(x, y, x + c.w - 1, y + c.h - 1, c.bgColor);
  441. G.GetMonoFontSize(font, fw, fh);
  442. tw := Strings.Length(c.text) * fw;
  443. tx := x + 2 - c.off;
  444. ty := y + (c.h - fh) DIV 2;
  445. G.DrawString(c.text, tx, ty, font, c.fgColor);
  446. IF c.focused THEN
  447. INC(tx, fw * c.pos - 1);
  448. G.VLine(tx, ty, ty + fh - 1, or);
  449. G.HLine(tx - 1, ty, tx + 1, or);
  450. G.HLine(tx - 1, ty + fh - 1, tx + 1, or)
  451. END;
  452. G.HLine(x, y, x + c.w - 2, c.fgColor);
  453. G.VLine(x, y, y + c.h - 1, c.fgColor);
  454. G.HLine(x + 1, y + c.h - 1, x + c.w - 1, or);
  455. G.VLine(x + c.w - 1, y, y + c.h - 1, or)
  456. END DrawEdit;
  457. PROCEDURE EditOnMouseDown*(c: Edit; VAR msg: MouseDownMsg);
  458. VAR n, fw, fh: INTEGER;
  459. BEGIN
  460. IF (msg.btn = 1) & (msg.x > 0) & (msg.x < c.w - 1) &
  461. (msg.y > 0) & (msg.y < c.h - 1)
  462. THEN
  463. G.GetMonoFontSize(font, fw, fh);
  464. n := (msg.x - 2 + fw DIV 2) DIV fw;
  465. IF n < 0 THEN n := 0 ELSIF n > c.len THEN n := c.len END;
  466. c.pos := n
  467. END
  468. END EditOnMouseDown;
  469. PROCEDURE EditCheckOffset(c: Edit);
  470. VAR n, fw, fh: INTEGER;
  471. BEGIN
  472. G.GetMonoFontSize(font, fw, fh);
  473. n := c.pos * fw - c.off;
  474. IF c.len * fw <= c.w - 4 THEN c.off := 0
  475. ELSIF n < 0 THEN c.off := c.pos * fw
  476. ELSIF n >= c.w - 4 THEN c.off := c.pos * fw - c.w + 4
  477. ELSIF c.len * fw - c.off <= c.w - 4 THEN c.off := c.len * fw - c.w + 4
  478. END
  479. END EditCheckOffset;
  480. PROCEDURE EditOnChar*(c: Edit; VAR msg: CharMsg);
  481. VAR i: INTEGER;
  482. BEGIN
  483. IF msg.key = G.kBackspace THEN
  484. IF c.pos > 0 THEN
  485. Strings.Delete(c.text, c.pos - 1, 1);
  486. DEC(c.len); DEC(c.pos)
  487. END
  488. ELSIF msg.key = G.kDel THEN
  489. IF c.pos < c.len THEN
  490. Strings.Delete(c.text, c.pos, 1);
  491. DEC(c.len)
  492. END
  493. ELSIF msg.ch < ' ' THEN
  494. IF msg.key = G.kLeft THEN DEC(c.pos)
  495. ELSIF msg.key = G.kRight THEN INC(c.pos)
  496. ELSIF msg.key = G.kHome THEN c.pos := 0
  497. ELSIF msg.key = G.kEnd THEN c.pos := c.len
  498. END;
  499. IF c.pos < 0 THEN c.pos := 0 ELSIF c.pos > c.len THEN c.pos := c.len END
  500. ELSIF c.len < LEN(c.text) - 1 THEN
  501. c.text[c.len + 1] := 0X;
  502. i := c.len;
  503. WHILE i > c.pos DO
  504. c.text[i] := c.text[i - 1];
  505. DEC(i)
  506. END;
  507. c.text[c.pos] := msg.ch;
  508. INC(c.len); INC(c.pos)
  509. END;
  510. EditCheckOffset(c)
  511. END EditOnChar;
  512. PROCEDURE EditHandler*(c: Widget; VAR msg: Message);
  513. VAR e: Edit;
  514. BEGIN e := c(Edit);
  515. IF msg IS DrawMsg THEN
  516. DrawEdit(e, msg(DrawMsg).x, msg(DrawMsg).y,
  517. msg(DrawMsg).w, msg(DrawMsg).h)
  518. ELSIF msg IS MouseDownMsg THEN EditOnMouseDown(e, msg(MouseDownMsg))
  519. ELSIF msg IS CharMsg THEN EditOnChar(e, msg(CharMsg))
  520. ELSE WidgetHandler(c, msg)
  521. END
  522. END EditHandler;
  523. PROCEDURE InitEdit*(c: Edit; where: Widget; x, y, w, h: INTEGER);
  524. BEGIN InitWidget(c, w, h);
  525. c.focusable := TRUE;
  526. G.MakeCol(c.bgColor, 255, 255, 255);
  527. c.text := 'Привет'; c.len := 6; c.pos := 2; c.off := 0;
  528. c.handle := EditHandler;
  529. Put(c, where, x, y)
  530. END InitEdit;
  531. PROCEDURE NewEdit*(where: Widget; x, y, w, h: INTEGER): Edit;
  532. VAR c: Edit;
  533. BEGIN NEW(c); InitEdit(c, where, x, y, w, h)
  534. RETURN c END NewEdit;
  535. PROCEDURE EditSetText*(c: Edit; text: ARRAY OF CHAR);
  536. BEGIN
  537. Strings.Copy(text, c.text);
  538. c.len := Strings.Length(text);
  539. c.pos := 0;
  540. c.off := 0
  541. END EditSetText;
  542. (** ScrollBar **)
  543. PROCEDURE DrawBox(x, y, w, h: INTEGER; bg, fg: G.Color);
  544. BEGIN
  545. G.FillRect(x, y, x + w - 1, y + h - 1, bg);
  546. G.Rect(x, y, x + w - 1, y + h - 1, fg)
  547. END DrawBox;
  548. PROCEDURE DrawHorizScrollBar(c: ScrollBar; x, y, w, h: INTEGER);
  549. VAR fw, fh, X, Y, hs, maxHs, pos, range: INTEGER;
  550. bs: INTEGER; (** Button size *)
  551. grey: G.Color;
  552. BEGIN
  553. G.MakeCol(grey, 80, 80, 80);
  554. DrawButtonBox(x, y, c.w, c.h, c.bgColor, c.parent.bgColor, TRUE, TRUE);
  555. hs := c.handleSize;
  556. bs := c.h;
  557. IF bs > 20 THEN bs := 20 END;
  558. c.btnSize := bs;
  559. maxHs := c.w - bs * 2 + 4;
  560. IF hs > maxHs THEN hs := maxHs END;
  561. range := c.max - c.min;
  562. pos := c.value;
  563. IF pos < c.min THEN pos := c.min ELSIF pos > c.max THEN pos := c.max END;
  564. c.handlePos := bs - 2 + ((maxHs - hs) * pos + range DIV 2) DIV range;
  565. DrawButtonBox(x, y, bs, c.h, c.bgColor,
  566. c.parent.bgColor, c.btnPressed = 1, FALSE);
  567. DrawButtonBox(x + c.w - bs, y, bs, c.h, c.bgColor,
  568. c.parent.bgColor, c.btnPressed = 2, TRUE);
  569. X := x + (bs - 1) DIV 2; Y := y + (bs - 1) DIV 2;
  570. IF c.btnPressed = 1 THEN INC(X); INC(Y) END;
  571. G.HLine(X - 4, Y, X + 4, c.fgColor);
  572. G.Line(X - 4, Y, X - 1, Y + 3, c.fgColor);
  573. G.Line(X - 4, Y, X - 1, Y - 3, c.fgColor);
  574. X := x + c.w - bs DIV 2 - 1;
  575. IF c.btnPressed = 1 THEN DEC(Y) END;
  576. IF c.btnPressed = 2 THEN INC(X); INC(Y) END;
  577. G.HLine(X - 4, Y, X + 4, c.fgColor);
  578. G.Line(X + 4, Y, X + 1, Y + 3, c.fgColor);
  579. G.Line(X + 4, Y, X + 1, Y - 3, c.fgColor);
  580. DrawButtonBox(x + c.handlePos, y, hs, c.h, c.bgColor,
  581. c.parent.bgColor, c.btnPressed = 3, FALSE)
  582. END DrawHorizScrollBar;
  583. PROCEDURE DrawVertScrollBar(c: ScrollBar; x, y, w, h: INTEGER);
  584. VAR fw, fh, X, Y, hs, maxHs, pos, range: INTEGER;
  585. bs: INTEGER; (** Button size *)
  586. grey: G.Color;
  587. BEGIN
  588. G.MakeCol(grey, 80, 80, 80);
  589. DrawButtonBox(x, y, c.w, c.h, c.bgColor, c.parent.bgColor, TRUE, TRUE);
  590. hs := c.handleSize;
  591. bs := c.w;
  592. IF bs > 20 THEN bs := 20 END;
  593. c.btnSize := bs;
  594. maxHs := c.h - bs * 2 + 4;
  595. IF hs > maxHs THEN hs := maxHs END;
  596. range := c.max - c.min;
  597. pos := c.value;
  598. IF pos < c.min THEN pos := c.min ELSIF pos > c.max THEN pos := c.max END;
  599. c.handlePos := bs - 2 + ((maxHs - hs) * pos + range DIV 2) DIV range;
  600. DrawButtonBox(x, y, c.w, bs, c.bgColor,
  601. c.parent.bgColor, c.btnPressed = 1, FALSE);
  602. DrawButtonBox(x, y + c.h - bs, c.w, bs, c.bgColor,
  603. c.parent.bgColor, c.btnPressed = 2, TRUE);
  604. X := x + (bs - 1) DIV 2; Y := y + (bs - 1) DIV 2;
  605. IF c.btnPressed = 1 THEN INC(X); INC(Y) END;
  606. G.VLine(X, Y - 4, Y + 4, c.fgColor);
  607. G.Line(X, Y - 4, X + 3, Y - 1, c.fgColor);
  608. G.Line(X, Y - 4, X - 3, Y - 1, c.fgColor);
  609. Y := y + c.h - bs DIV 2 - 1;
  610. IF c.btnPressed = 1 THEN DEC(X) END;
  611. IF c.btnPressed = 2 THEN INC(X); INC(Y) END;
  612. G.VLine(X, Y - 4, Y + 4, c.fgColor);
  613. G.Line(X, Y + 4, X + 3, Y + 1, c.fgColor);
  614. G.Line(X, Y + 4, X - 3, Y + 1, c.fgColor);
  615. DrawButtonBox(x, y + c.handlePos, c.w, hs, c.bgColor,
  616. c.parent.bgColor, c.btnPressed = 3, FALSE)
  617. END DrawVertScrollBar;
  618. PROCEDURE DrawScrollBar*(c: ScrollBar; x, y, w, h: INTEGER);
  619. BEGIN
  620. IF c.vertical THEN
  621. DrawVertScrollBar(c, x, y, w, h)
  622. ELSE
  623. DrawHorizScrollBar(c, x, y, w, h)
  624. END
  625. END DrawScrollBar;
  626. PROCEDURE ScrollBarSetVertical*(c: ScrollBar; vertical: BOOLEAN);
  627. BEGIN c.vertical := vertical
  628. END ScrollBarSetVertical;
  629. PROCEDURE ScrollBarSetValue*(c: ScrollBar; value: INTEGER);
  630. BEGIN
  631. IF value < c.min THEN value := c.min
  632. ELSIF value > c.max THEN value := c.max
  633. END;
  634. IF c.value # value THEN
  635. c.value := value;
  636. IF c.onScroll # NIL THEN c.onScroll(c, value) END
  637. END
  638. END ScrollBarSetValue;
  639. PROCEDURE HandleScrollBarMouseMove(c: ScrollBar; VAR msg: MouseMoveMsg);
  640. VAR n, x, size, w: INTEGER;
  641. BEGIN
  642. IF c.handlePressed THEN
  643. IF c.vertical THEN x := msg.y; size := c.h
  644. ELSE x := msg.x; size := c.w
  645. END;
  646. w := size - c.btnSize * 2 - c.handleSize;
  647. n := x - c.handlePressPos - c.btnSize;
  648. n := (n * (c.max - c.min) + w DIV 2) DIV w + c.min;
  649. ScrollBarSetValue(c, n)
  650. END
  651. END HandleScrollBarMouseMove;
  652. PROCEDURE HandleScrollBarMouseDown(c: ScrollBar; VAR msg: MouseDownMsg);
  653. VAR x, d, size: INTEGER;
  654. BEGIN
  655. IF c.vertical THEN x := msg.y; size := c.h
  656. ELSE x := msg.x; size := c.w
  657. END;
  658. IF msg.btn = 2 THEN d := 1 ELSE d := c.inc END;
  659. IF x < c.btnSize THEN
  660. c.btnPressed := 1(*Less btn*);
  661. ScrollBarSetValue(c, c.value - d);
  662. ELSIF x >= size - c.btnSize THEN
  663. c.btnPressed := 2(*More btn*);
  664. ScrollBarSetValue(c, c.value + d)
  665. ELSIF msg.btn = 1 THEN
  666. IF (c.handlePos <= x) & (x < c.handlePos + c.handleSize) THEN
  667. c.btnPressed := 3(*Handle*);
  668. c.handlePressed := TRUE;
  669. c.handlePressPos := x - c.handlePos
  670. ELSIF x < c.handlePos THEN
  671. ScrollBarSetValue(c, c.value - c.bigInc)
  672. ELSE
  673. ScrollBarSetValue(c, c.value + c.bigInc)
  674. END
  675. ELSE c.btnPressed := 0(*Nothing*);
  676. END;
  677. WidgetHandler(c, msg)
  678. END HandleScrollBarMouseDown;
  679. PROCEDURE ScrollBarHandler*(c: Widget; VAR msg: Message);
  680. VAR s: ScrollBar;
  681. BEGIN s := c(ScrollBar);
  682. IF msg IS DrawMsg THEN
  683. DrawScrollBar(s, msg(DrawMsg).x, msg(DrawMsg).y,
  684. msg(DrawMsg).w, msg(DrawMsg).h)
  685. ELSIF msg IS MouseMoveMsg THEN HandleScrollBarMouseMove(s, msg(MouseMoveMsg))
  686. ELSIF msg IS MouseDownMsg THEN HandleScrollBarMouseDown(s, msg(MouseDownMsg))
  687. ELSIF msg IS MouseUpMsg THEN s.handlePressed := FALSE; s.btnPressed := 0(*Nothing*)
  688. ELSE WidgetHandler(c, msg)
  689. END
  690. END ScrollBarHandler;
  691. PROCEDURE InitScrollBar*(c: ScrollBar; where: Widget;
  692. x, y, w, h: INTEGER);
  693. BEGIN InitWidget(c, w, h);
  694. c.handle := ScrollBarHandler;
  695. c.value := 0; c.min := 0; c.max := 100; c.inc := 5; c.bigInc := 20;
  696. c.handlePos := 0; c.handleSize := 24; c.btnSize := 0;
  697. c.btnPressed := 0(*Nothing*);
  698. Put(c, where, x, y)
  699. END InitScrollBar;
  700. PROCEDURE NewScrollBar*(where: Widget; x, y, w, h: INTEGER): ScrollBar;
  701. VAR c: ScrollBar;
  702. BEGIN NEW(c); InitScrollBar(c, where, x, y, w, h)
  703. RETURN c END NewScrollBar;
  704. PROCEDURE SetOnScroll*(c: ScrollBar; proc: PROCEDURE (c: Widget; value: INTEGER));
  705. BEGIN c.onScroll := proc
  706. END SetOnScroll;
  707. (** General **)
  708. PROCEDURE DrawAll*;
  709. VAR c: Widget;
  710. BEGIN
  711. c := app.body;
  712. REPEAT
  713. DrawForm(c(Form));
  714. c := c.next
  715. UNTIL c = app.body;
  716. G.Flip
  717. END DrawAll;
  718. PROCEDURE HandleMouseMove(VAR e: G.Event);
  719. VAR c: Widget;
  720. BEGIN
  721. c := FindHoveredInRing(app.body, e.x, e.y, FALSE);
  722. IF c # NIL THEN
  723. WidgetHandleMouseMove(c, e.x - c.x, e.y - c.y, e.button)
  724. END
  725. END HandleMouseMove;
  726. PROCEDURE HandleMouseDown(VAR e: G.Event);
  727. VAR c: Widget;
  728. BEGIN
  729. pressedX := 0; pressedY := 0;
  730. c := FindHoveredInRing(app.body, e.x, e.y, TRUE);
  731. IF c # NIL THEN
  732. WidgetHandleMouseDown(c, e.x - c.x, e.y - c.y, e.button)
  733. END
  734. END HandleMouseDown;
  735. PROCEDURE HandleMouseUp(VAR e: G.Event);
  736. VAR c: Widget;
  737. BEGIN
  738. IF pressedWidget # NIL THEN
  739. c := pressedWidget;
  740. IF ~c.hovered THEN c := NIL END;
  741. WidgetOnMouseUp(pressedWidget, e.x - pressedX, e.y - pressedY, e.button);
  742. IF (c # NIL) & (e.button = 1) THEN
  743. WidgetOnClick(c)
  744. END
  745. END
  746. END HandleMouseUp;
  747. PROCEDURE HandleKeyDown(VAR e: G.Event);
  748. VAR msg: KeyDownMsg;
  749. BEGIN
  750. IF focusedWidget # NIL THEN
  751. msg.key := e.key;
  752. focusedWidget.handle(focusedWidget, msg)
  753. END
  754. END HandleKeyDown;
  755. PROCEDURE HandleKeyUp(VAR e: G.Event);
  756. VAR msg: KeyUpMsg;
  757. BEGIN
  758. IF focusedWidget # NIL THEN
  759. msg.key := e.key;
  760. focusedWidget.handle(focusedWidget, msg)
  761. END
  762. END HandleKeyUp;
  763. PROCEDURE HandleChar(VAR e: G.Event);
  764. VAR msg: CharMsg;
  765. BEGIN
  766. IF focusedWidget # NIL THEN
  767. msg.key := e.key; msg.ch := e.ch;
  768. msg.mod := e.mod; msg.repeat := e.repeat;
  769. focusedWidget.handle(focusedWidget, msg)
  770. END
  771. END HandleChar;
  772. PROCEDURE HandleEvent(VAR e: G.Event);
  773. BEGIN
  774. IF e.type = G.quit THEN quit := TRUE
  775. ELSIF e.type = G.mouseMove THEN HandleMouseMove(e)
  776. ELSIF e.type = G.mouseDown THEN HandleMouseDown(e)
  777. ELSIF e.type = G.mouseUp THEN HandleMouseUp(e)
  778. ELSIF e.type = G.keyDown THEN HandleKeyDown(e)
  779. ELSIF e.type = G.keyUp THEN HandleKeyUp(e)
  780. ELSIF e.type = G.char THEN HandleChar(e)
  781. END
  782. END HandleEvent;
  783. PROCEDURE Run*;
  784. VAR e: G.Event;
  785. BEGIN
  786. quit := FALSE;
  787. REPEAT
  788. WHILE G.HasEvents() DO
  789. G.WaitEvent(e);
  790. HandleEvent(e)
  791. END;
  792. DrawAll
  793. UNTIL quit
  794. END Run;
  795. PROCEDURE Init*;
  796. BEGIN
  797. font := G.LoadFont('Data/Fonts/Main');
  798. IF font = NIL THEN Out.String('SimpleGui: could not load font.'); Out.Ln END;
  799. Done := font # NIL;
  800. app := NewApp();
  801. hoveredWidget := NIL; pressedWidget := NIL;
  802. pressedX := 0; pressedY := 0
  803. END Init;
  804. END SimpleGui.