2
0

SimpleGui.Mod 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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*: INTEGER END;
  8. MouseDownMsg* = RECORD(Message) x*, y*: INTEGER END;
  9. MouseUpMsg* = RECORD(Message) x*, y*: INTEGER END;
  10. MouseEnterMsg* = RECORD(Message) END;
  11. MouseLeaveMsg* = RECORD(Message) END;
  12. ClickMsg* = RECORD(Message) END;
  13. Handler* = PROCEDURE (c: Widget; VAR msg: Message);
  14. WidgetDesc* = RECORD
  15. x*, y*, w*, h*: INTEGER;
  16. bgColor*, fgColor*: G.Color;
  17. hovered*: BOOLEAN; (** TRUE if mouse pointer is over the widget *)
  18. pressed*: BOOLEAN; (** TRUE if widget is held down with LMB *)
  19. body*: Widget;
  20. next*: Widget;
  21. handle*: Handler;
  22. onMouseDown*: PROCEDURE (c: Widget; x, y: INTEGER);
  23. onMouseUp*: PROCEDURE (c: Widget; x, y: INTEGER);
  24. onMouseMove*: PROCEDURE (c: Widget; x, y: INTEGER);
  25. onMouseEnter*: PROCEDURE (c: Widget);
  26. onMouseLeave*: PROCEDURE (c: Widget);
  27. onClick*: PROCEDURE (c: Widget);
  28. END;
  29. Panel* = POINTER TO PanelDesc;
  30. PanelDesc* = RECORD(WidgetDesc) END;
  31. Form* = POINTER TO FormDesc;
  32. FormDesc* = RECORD(PanelDesc) END;
  33. Button* = POINTER TO ButtonDesc;
  34. ButtonDesc* = RECORD(WidgetDesc)
  35. caption*: ARRAY 64 OF CHAR
  36. ;X*, Y*: INTEGER
  37. END;
  38. VAR
  39. Done*: BOOLEAN; (** FALSE after a failed opration and before the next Init *)
  40. forms*: Form;
  41. font*: G.Font;
  42. quit: BOOLEAN; (** Main loop in procedure Run ends when TRUE *)
  43. hoveredWidget: Widget;
  44. pressedWidget: Widget;
  45. pressedX, pressedY: INTEGER;
  46. (** Widget **)
  47. PROCEDURE FindHoveredInList(list: Widget; x, y: INTEGER;
  48. forMouseDown: BOOLEAN): Widget;
  49. VAR c: Widget;
  50. BEGIN c := list;
  51. WHILE (c # NIL) &
  52. ~((c.x <= x) & (x < c.x + c.w) &
  53. (c.y <= y) & (y < c.y + c.h))
  54. DO c := c.next
  55. END;
  56. IF forMouseDown & (c # NIL) THEN
  57. INC(pressedX, c.x); INC(pressedY, c.y)
  58. END
  59. RETURN c END FindHoveredInList;
  60. PROCEDURE WidgetOnMouseEnter*(c: Widget);
  61. VAR msg: MouseEnterMsg;
  62. BEGIN
  63. IF pressedWidget = c THEN c.pressed := TRUE END;
  64. c.handle(c, msg)
  65. END WidgetOnMouseEnter;
  66. PROCEDURE WidgetOnMouseLeave*(c: Widget);
  67. VAR msg: MouseLeaveMsg;
  68. BEGIN
  69. c.pressed := FALSE;
  70. c.handle(c, msg)
  71. END WidgetOnMouseLeave;
  72. PROCEDURE WidgetOnMouseMove*(c: Widget; x, y: INTEGER);
  73. VAR msg: MouseMoveMsg;
  74. BEGIN
  75. IF (0 <= x) & (x < c.w) & (0 <= y) & (y < c.h) THEN
  76. IF c # hoveredWidget THEN
  77. IF hoveredWidget # NIL THEN WidgetOnMouseLeave(hoveredWidget) END;
  78. hoveredWidget := c;
  79. WidgetOnMouseEnter(hoveredWidget)
  80. END
  81. ELSIF c = hoveredWidget THEN
  82. WidgetOnMouseLeave(c);
  83. hoveredWidget := NIL
  84. END;
  85. msg.x := x; msg.y := y;
  86. c.handle(c, msg);
  87. IF c.onMouseMove # NIL THEN c.onMouseMove(c, x, y) END
  88. END WidgetOnMouseMove;
  89. PROCEDURE WidgetHandleMouseMove*(c: Widget; x, y: INTEGER);
  90. VAR p: Widget;
  91. BEGIN
  92. IF pressedWidget # NIL THEN
  93. WidgetOnMouseMove(pressedWidget, x - pressedX, y - pressedY)
  94. ELSE
  95. p := FindHoveredInList(c.body, x, y, FALSE);
  96. IF p # NIL THEN
  97. WidgetHandleMouseMove(p, x - p.x, y - p.y)
  98. ELSE
  99. WidgetOnMouseMove(c, x, y)
  100. END
  101. END
  102. END WidgetHandleMouseMove;
  103. PROCEDURE WidgetOnMouseDown*(c: Widget; x, y: INTEGER);
  104. VAR msg: MouseDownMsg;
  105. BEGIN
  106. pressedWidget := c;
  107. msg.x := x; msg.y := y;
  108. c.handle(c, msg);
  109. IF c.onMouseDown # NIL THEN c.onMouseDown(c, x, y) END
  110. END WidgetOnMouseDown;
  111. PROCEDURE WidgetHandleMouseDown*(c: Widget; x, y: INTEGER);
  112. VAR p: Widget;
  113. BEGIN
  114. p := FindHoveredInList(c.body, x, y, TRUE);
  115. IF p # NIL THEN
  116. WidgetHandleMouseDown(p, x - p.x, y - p.y)
  117. ELSE
  118. WidgetOnMouseDown(c, x, y)
  119. END
  120. END WidgetHandleMouseDown;
  121. PROCEDURE WidgetOnMouseUp*(c: Widget; x, y: INTEGER);
  122. VAR msg: MouseUpMsg;
  123. BEGIN
  124. pressedWidget := NIL;
  125. msg.x := x; msg.y := y;
  126. c.handle(c, msg);
  127. IF c.onMouseUp # NIL THEN c.onMouseUp(c, x, y) END
  128. END WidgetOnMouseUp;
  129. PROCEDURE WidgetOnClick*(c: Widget);
  130. VAR msg: ClickMsg;
  131. BEGIN c.handle(c, msg);
  132. IF c.onClick # NIL THEN c.onClick(c) END
  133. END WidgetOnClick;
  134. PROCEDURE WidgetHandler*(c: Widget; VAR msg: Message);
  135. VAR x, y: INTEGER;
  136. BEGIN
  137. IF msg IS DrawMsg THEN
  138. x := msg(DrawMsg).x; y := msg(DrawMsg).y;
  139. G.FillRect(x, y, x + c.w - 1, y + c.h - 1, c.bgColor);
  140. G.Rect(x, y, x + c.w - 1, y + c.h - 1, c.fgColor);
  141. G.Rect(x + 2, y + 2, x + c.w - 3, y + c.h - 3, c.fgColor)
  142. END
  143. END WidgetHandler;
  144. PROCEDURE DrawWidget*(c: Widget; x, y, w, h: INTEGER);
  145. VAR M: DrawMsg;
  146. BEGIN
  147. M.x := x; M.y := y; M.w := w; M.h := h;
  148. c.handle(c, M)
  149. END DrawWidget;
  150. PROCEDURE DrawBody*(c: Widget; x, y, w, h: INTEGER);
  151. VAR p: Widget;
  152. x2, y2, w2, h2: INTEGER;
  153. cx, cy, cw, ch: INTEGER;
  154. BEGIN
  155. p := c.body;
  156. WHILE p # NIL DO
  157. x2 := x + p.x; y2 := y + p.y;
  158. w2 := w - p.x; h2 := h - p.y;
  159. (* !FIXME clip x2,y2,w2,h2 to not more than x,y,w,h*)
  160. cx := x2; cy := y2; cw := p.w; ch := p.h;
  161. IF cx + cw > x + w THEN cw := x + w - cx END;
  162. IF cy + ch > y + h THEN ch := y + h - cy END;
  163. IF cx < x THEN DEC(cw, x - cx); cx := x END;
  164. IF cy < y THEN DEC(ch, y - cy); cy := y END;
  165. G.SetClip(cx, cy, cw, ch);
  166. DrawWidget(p, x2, y2, p.w, p.h);
  167. p := p.next
  168. END;
  169. G.UnsetClip
  170. END DrawBody;
  171. PROCEDURE SetBgColor*(c: Widget; color: G.Color);
  172. BEGIN c.bgColor := color
  173. END SetBgColor;
  174. PROCEDURE SetFgColor*(c: Widget; color: G.Color);
  175. BEGIN c.fgColor := color
  176. END SetFgColor;
  177. PROCEDURE SetOnMouseMove*(c: Widget; proc: PROCEDURE (c: Widget; x, y: INTEGER));
  178. BEGIN c.onMouseMove := proc
  179. END SetOnMouseMove;
  180. PROCEDURE SetOnMouseDown*(c: Widget; proc: PROCEDURE (c: Widget; x, y: INTEGER));
  181. BEGIN c.onMouseDown := proc
  182. END SetOnMouseDown;
  183. PROCEDURE SetOnMouseUp*(c: Widget; proc: PROCEDURE (c: Widget; x, y: INTEGER));
  184. BEGIN c.onMouseUp := proc
  185. END SetOnMouseUp;
  186. PROCEDURE SetOnClick*(c: Widget; proc: PROCEDURE (c: Widget));
  187. BEGIN c.onClick := proc
  188. END SetOnClick;
  189. PROCEDURE InitWidget*(c: Widget; w, h: INTEGER);
  190. BEGIN c.x := 0; c.y := 0; c.w := w; c.h := h;
  191. G.MakeCol(c.bgColor, 180, 180, 180);
  192. G.MakeCol(c.fgColor, 0, 0, 0);
  193. c.handle := WidgetHandler;
  194. c.body := NIL; c.next := NIL
  195. END InitWidget;
  196. PROCEDURE Put*(c, where: Widget; x, y: INTEGER);
  197. VAR p: Widget;
  198. BEGIN
  199. IF c # NIL THEN
  200. c.x := x; c.y := y;
  201. IF where # NIL THEN
  202. c.next := NIL;
  203. p := where.body;
  204. IF p = NIL THEN
  205. where.body := c
  206. ELSE
  207. WHILE p.next # NIL DO p := p.next END;
  208. p.next := c
  209. END
  210. END
  211. END
  212. END Put;
  213. (** Panel **)
  214. PROCEDURE PanelHandler*(c: Widget; VAR msg: Message);
  215. VAR x, y: INTEGER;
  216. BEGIN
  217. IF msg IS DrawMsg THEN
  218. x := msg(DrawMsg).x; y := msg(DrawMsg).y;
  219. G.FillRect(x, y, x + c.w - 1, y + c.h - 1, c.bgColor);
  220. DrawBody(c, x, y, c.w, c.h)
  221. ELSE WidgetHandler(c, msg)
  222. END
  223. END PanelHandler;
  224. PROCEDURE InitPanel*(c: Panel; where: Widget; x, y, w, h: INTEGER);
  225. BEGIN InitWidget(c, w, h);
  226. c.handle := PanelHandler;
  227. Put(c, where, x, y)
  228. END InitPanel;
  229. PROCEDURE NewPanel*(where: Widget; x, y, w, h: INTEGER): Panel;
  230. VAR c: Panel;
  231. BEGIN NEW(c); InitPanel(c, where, x, y, w, h)
  232. RETURN c END NewPanel;
  233. (** Form **)
  234. PROCEDURE DrawForm*(c: Form);
  235. BEGIN
  236. G.FillRect(c.x, c.y, c.x + c.w - 1, c.y + c.h - 1, c.bgColor);
  237. DrawBody(c, c.x, c.y, c.w, c.h)
  238. END DrawForm;
  239. PROCEDURE FormHandler*(c: Widget; VAR msg: Message);
  240. BEGIN WidgetHandler(c, msg)
  241. END FormHandler;
  242. PROCEDURE InitForm*(c: Form; x, y, w, h: INTEGER);
  243. BEGIN InitPanel(c, NIL, x, y, w, h);
  244. c.handle := FormHandler;
  245. c.next := forms; forms := c
  246. END InitForm;
  247. PROCEDURE NewForm*(x, y, w, h: INTEGER): Form;
  248. VAR c: Form;
  249. BEGIN NEW(c); InitForm(c, x, y, w, h);
  250. RETURN c END NewForm;
  251. (** Button **)
  252. PROCEDURE DrawButton*(c: Button; x, y, w, h: INTEGER);
  253. VAR cw, ch, tw, tx, ty: INTEGER;
  254. down: BOOLEAN;
  255. Z: G.Color;
  256. BEGIN
  257. down := c(Button).pressed & c(Button).hovered;
  258. G.FillRect(x, y, x + c.w - 1, y + c.h - 1, c.bgColor);
  259. ;G.MakeCol(Z, 255, 128, 0);
  260. ;G.Line(x + c.h DIV 4, y + c.h DIV 2, x + c(Button).X, y + c(Button).Y, Z);
  261. ;G.MakeCol(Z, 215, 0, 0);
  262. ;G.Line(x + c.h DIV 4, y + c.h DIV 2 + 1, x + c(Button).X, y + c(Button).Y + 1, Z);
  263. G.Rect(x, y, x + c.w - 1, y + c.h - 1, c.fgColor);
  264. IF ~down THEN
  265. G.Rect(x, y, x + c.w - 2, y + c.h - 2, c.fgColor)
  266. END;
  267. G.GetMonoFontSize(font, cw, ch);
  268. tw := Strings.Length(c.caption) * cw;
  269. tx := x + (c.w - tw) DIV 2;
  270. ty := y + (c.h - ch) DIV 2;
  271. IF down THEN INC(tx); INC(ty) END;
  272. G.DrawString(c.caption, tx, ty, font, c.fgColor)
  273. END DrawButton;
  274. PROCEDURE BMM(c: Button; x, y: INTEGER);
  275. BEGIN
  276. c.X := x; c.Y := y
  277. END BMM;
  278. PROCEDURE ButtonHandler*(c: Widget; VAR msg: Message);
  279. VAR b: Button;
  280. BEGIN b := c(Button);
  281. IF msg IS DrawMsg THEN
  282. DrawButton(b, msg(DrawMsg).x, msg(DrawMsg).y,
  283. msg(DrawMsg).w, msg(DrawMsg).h)
  284. ELSIF msg IS MouseMoveMsg THEN BMM(b, msg(MouseMoveMsg).x, msg(MouseMoveMsg).y)
  285. ELSIF msg IS MouseEnterMsg THEN b.hovered := TRUE
  286. ELSIF msg IS MouseLeaveMsg THEN b.hovered := FALSE
  287. ELSIF msg IS MouseDownMsg THEN b.pressed := TRUE
  288. ELSIF msg IS MouseUpMsg THEN b.pressed := FALSE
  289. ELSE WidgetHandler(c, msg)
  290. END
  291. END ButtonHandler;
  292. PROCEDURE InitButton*(c: Button; where: Widget;
  293. x, y, w, h: INTEGER; caption: ARRAY OF CHAR);
  294. BEGIN InitWidget(c, w, h);
  295. Strings.Copy(caption, c.caption);
  296. c.hovered := FALSE;
  297. c.pressed := FALSE;
  298. c.handle := ButtonHandler;
  299. Put(c, where, x, y)
  300. ;c.X := 0; c.Y := 0;
  301. END InitButton;
  302. PROCEDURE NewButton*(where: Widget; x, y, w, h: INTEGER; caption: ARRAY OF CHAR): Button;
  303. VAR c: Button;
  304. BEGIN NEW(c); InitButton(c, where, x, y, w, h, caption)
  305. RETURN c END NewButton;
  306. (** General **)
  307. PROCEDURE DrawAll*;
  308. VAR c: Widget;
  309. BEGIN
  310. c := forms;
  311. WHILE c # NIL DO
  312. DrawForm(c(Form));
  313. c := c.next
  314. END;
  315. G.Flip
  316. END DrawAll;
  317. PROCEDURE HandleMouseMove(VAR e: G.Event);
  318. VAR c: Widget;
  319. BEGIN
  320. c := FindHoveredInList(forms, e.x, e.y, FALSE);
  321. IF c # NIL THEN
  322. WidgetHandleMouseMove(c, e.x - c.x, e.y - c.y)
  323. END
  324. END HandleMouseMove;
  325. PROCEDURE HandleMouseDown(VAR e: G.Event);
  326. VAR c: Widget;
  327. BEGIN
  328. pressedX := 0; pressedY := 0;
  329. c := FindHoveredInList(forms, e.x, e.y, TRUE);
  330. IF c # NIL THEN
  331. WidgetHandleMouseDown(c, e.x - c.x, e.y - c.y)
  332. END
  333. END HandleMouseDown;
  334. PROCEDURE HandleMouseUp(VAR e: G.Event);
  335. VAR c: Widget;
  336. BEGIN
  337. IF pressedWidget # NIL THEN
  338. c := pressedWidget;
  339. IF ~c.hovered THEN c := NIL END;
  340. WidgetOnMouseUp(pressedWidget, e.x - pressedX, e.y - pressedY);
  341. IF c # NIL THEN
  342. WidgetOnClick(c)
  343. END
  344. END
  345. END HandleMouseUp;
  346. PROCEDURE HandleEvent(VAR e: G.Event);
  347. BEGIN
  348. IF e.type = G.quit THEN quit := TRUE
  349. ELSIF e.type = G.mouseMove THEN HandleMouseMove(e)
  350. ELSIF e.type = G.mouseDown THEN HandleMouseDown(e)
  351. ELSIF e.type = G.mouseUp THEN HandleMouseUp(e)
  352. END
  353. END HandleEvent;
  354. PROCEDURE Run*;
  355. VAR e: G.Event;
  356. BEGIN
  357. quit := FALSE;
  358. REPEAT
  359. WHILE G.HasEvents() DO
  360. G.WaitEvent(e);
  361. HandleEvent(e)
  362. END;
  363. DrawAll
  364. UNTIL quit
  365. END Run;
  366. PROCEDURE Init*;
  367. BEGIN
  368. forms := NIL;
  369. font := G.LoadFont('Data/Fonts/Main');
  370. IF font = NIL THEN Out.String('SimpleGui: could not load font.'); Out.Ln END;
  371. Done := font # NIL;
  372. hoveredWidget := NIL; pressedWidget := NIL;
  373. pressedX := 0; pressedY := 0
  374. END Init;
  375. END SimpleGui.