SimpleGui.Mod 26 KB

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