Răsfoiți Sursa

SimpleGui: Edit widget

Arthur Yefimov 1 an în urmă
părinte
comite
177ea8677f
2 a modificat fișierele cu 283 adăugiri și 92 ștergeri
  1. 13 10
      Programs/Examples/MapEditor.Mod
  2. 270 82
      Programs/Examples/SimpleGui.Mod

+ 13 - 10
Programs/Examples/MapEditor.Mod

@@ -3,12 +3,13 @@ IMPORT G := Graph, S := SimpleGui, Out;
 
 VAR
   frmMain: S.Form;
-  pnlButtons: S.Panel;
+  pnlSide: S.Panel;
   btnSave: S.Button;
   btnExit: S.Button;
   pnlMap: S.Panel;
   btn2: S.Button;
   btnMove: S.Button;
+  edtText: S.Edit;
 
   XX, YY: INTEGER;
   moving: BOOLEAN;
@@ -18,22 +19,23 @@ BEGIN
   c(S.Button).caption[1] := CHR((ORD(c(S.Button).caption[1]) + 1) MOD 1256)
 END Btn2OnClick;
 
-PROCEDURE BtnExitOnMouseMove(c: S.Widget; x, y: INTEGER);
+PROCEDURE BtnExitOnMouseMove(c: S.Widget; x, y, btn: INTEGER);
 BEGIN
   c(S.Button).caption[5] := CHR((ORD(c(S.Button).caption[5]) + 1) MOD 256);
   c(S.Button).caption[6] := CHR(ORD(c(S.Button).caption[5]) + 1);
   c(S.Button).caption[7] := 0X
 END BtnExitOnMouseMove;
 
-PROCEDURE BtnMoveOnMouseDown(c: S.Widget; x, y: INTEGER);
-BEGIN moving := TRUE; XX := x; YY := y
+PROCEDURE BtnMoveOnMouseDown(c: S.Widget; x, y, btn: INTEGER);
+BEGIN
+  IF btn = 1 THEN moving := TRUE; XX := x; YY := y END
 END BtnMoveOnMouseDown;
 
-PROCEDURE BtnMoveOnMouseUp(c: S.Widget; x, y: INTEGER);
+PROCEDURE BtnMoveOnMouseUp(c: S.Widget; x, y, btn: INTEGER);
 BEGIN moving := FALSE
 END BtnMoveOnMouseUp;
 
-PROCEDURE BtnMoveOnMouseMove(c: S.Widget; x, y: INTEGER);
+PROCEDURE BtnMoveOnMouseMove(c: S.Widget; x, y, btn: INTEGER);
 BEGIN
   IF moving THEN
     c.x := c.x + x - XX;
@@ -50,12 +52,13 @@ BEGIN
   G.GetScreenSize(W, H);
   frmMain := S.NewForm(0, 0, W, H);
 
-  pnlButtons := S.NewPanel(frmMain, 4, 4, 104, 60);
+  pnlSide := S.NewPanel(frmMain, 4, 4, 104, 84);
   G.MakeCol(color, 40, 150, 40);
-  S.SetBgColor(pnlButtons, color);
-  btnSave := S.NewButton(pnlButtons, 4, 4, 96, 24, 'Сохранить');
-  btnExit := S.NewButton(pnlButtons, 4, 32, 96, 24, 'Выйти');
+  S.SetBgColor(pnlSide, color);
+  btnSave := S.NewButton(pnlSide, 4, 4, 96, 24, 'Сохранить');
+  btnExit := S.NewButton(pnlSide, 4, 32, 96, 24, 'Выйти');
   S.SetOnMouseMove(btnExit, BtnExitOnMouseMove);
+  edtText := S.NewEdit(pnlSide, 4, 60, 96, 20);
 
   pnlMap := S.NewPanel(frmMain, 112, 4, W - 116, H - 8);
   G.MakeCol(color, 150, 90, 40);

+ 270 - 82
Programs/Examples/SimpleGui.Mod

@@ -6,30 +6,45 @@ TYPE
 
   Message* = RECORD END;
   DrawMsg* = RECORD(Message) x*, y*, w*, h*: INTEGER END;
-  MouseMoveMsg* = RECORD(Message) x*, y*: INTEGER END;
-  MouseDownMsg* = RECORD(Message) x*, y*: INTEGER END;
-  MouseUpMsg* = RECORD(Message) x*, y*: INTEGER END;
+  MouseMoveMsg* = RECORD(Message) x*, y*, btn*: INTEGER END;
+  MouseDownMsg* = RECORD(Message) x*, y*, btn*: INTEGER END;
+  MouseUpMsg* = RECORD(Message) x*, y*, btn*: INTEGER END;
   MouseEnterMsg* = RECORD(Message) END;
   MouseLeaveMsg* = RECORD(Message) END;
   ClickMsg* = RECORD(Message) END;
+  GetFocusMsg* = RECORD(Message) END;
+  LostFocusMsg* = RECORD(Message) END;
+  KeyDownMsg* = RECORD(Message) key*: INTEGER END;
+  KeyUpMsg* = RECORD(Message) key*: INTEGER END;
+  CharMsg* = RECORD(Message)
+    key*: INTEGER;
+    ch*: CHAR;
+    mod*: SET;
+    repeat*: BOOLEAN
+  END;
 
   Handler* = PROCEDURE (c: Widget; VAR msg: Message);
 
   WidgetDesc* = RECORD
     x*, y*, w*, h*: INTEGER;
     bgColor*, fgColor*: G.Color;
+    focusable*: BOOLEAN; (** TRUE if widget can get focus *)
+    focused*: BOOLEAN; (** TRUE if this widget is globally in focus *)
     hovered*: BOOLEAN; (** TRUE if mouse pointer is over the widget *)
     pressed*: BOOLEAN; (** TRUE if widget is held down with LMB *)
-    body*: Widget;
-    next*: Widget;
+    body*: Widget; (** Ring *)
+    prev*, next*, parent*: Widget;
     handle*: Handler;
 
-    onMouseDown*: PROCEDURE (c: Widget; x, y: INTEGER);
-    onMouseUp*: PROCEDURE (c: Widget; x, y: INTEGER);
-    onMouseMove*: PROCEDURE (c: Widget; x, y: INTEGER);
+    onMouseDown*: PROCEDURE (c: Widget; x, y, btn: INTEGER);
+    onMouseUp*: PROCEDURE (c: Widget; x, y, btn: INTEGER);
+    onMouseMove*: PROCEDURE (c: Widget; x, y, btn: INTEGER);
     onMouseEnter*: PROCEDURE (c: Widget);
     onMouseLeave*: PROCEDURE (c: Widget);
     onClick*: PROCEDURE (c: Widget);
+    onKeyDown*: PROCEDURE (c: Widget; key: INTEGER);
+    onKeyUp*: PROCEDURE (c: Widget; key: INTEGER);
+    onChar*: PROCEDURE (c: Widget; key: INTEGER; ch: CHAR; mod: SET; repeat: BOOLEAN);
   END;
 
   Panel* = POINTER TO PanelDesc;
@@ -44,9 +59,18 @@ TYPE
     ;X*, Y*: INTEGER
   END;
 
+  Edit* = POINTER TO EditDesc;
+  EditDesc* = RECORD(WidgetDesc)
+    text*: ARRAY 256 OF CHAR;
+    len*: INTEGER; (** Length of text in characters *)
+    pos*: INTEGER; (** Position of text carret, in range [0; len] *)
+    off*: INTEGER (** Used to slide text that does not fit, normal is 0 *)
+  END;
+
 VAR
   Done*: BOOLEAN; (** FALSE after a failed opration and before the next Init *)
-  forms*: Form;
+  forms*: Widget;
+  focusedWidget*: Widget; (** The widget with focus = TRUE *)
   font*: G.Font;
   quit: BOOLEAN; (** Main loop in procedure Run ends when TRUE *)
   hoveredWidget: Widget;
@@ -58,14 +82,19 @@ VAR
 PROCEDURE FindHoveredInList(list: Widget; x, y: INTEGER;
     forMouseDown: BOOLEAN): Widget;
 VAR c: Widget;
-BEGIN c := list;
-  WHILE (c # NIL) &
-        ~((c.x <= x) & (x < c.x + c.w) &
-          (c.y <= y) & (y < c.y + c.h))
-  DO c := c.next
-  END;
-  IF forMouseDown & (c # NIL) THEN
-    INC(pressedX, c.x); INC(pressedY, c.y)
+BEGIN
+  IF list # NIL THEN
+    c := list.prev;
+    WHILE (c # NIL) &
+          ~((c.x <= x) & (x < c.x + c.w) &
+            (c.y <= y) & (y < c.y + c.h))
+    DO
+      IF c = list THEN c := NIL ELSE c := c.prev END
+    END;
+    IF forMouseDown & (c # NIL) THEN
+      INC(pressedX, c.x); INC(pressedY, c.y)
+    END
+  ELSE c := NIL
   END
 RETURN c END FindHoveredInList;
 
@@ -83,7 +112,7 @@ BEGIN
   c.handle(c, msg)
 END WidgetOnMouseLeave;
 
-PROCEDURE WidgetOnMouseMove*(c: Widget; x, y: INTEGER);
+PROCEDURE WidgetOnMouseMove*(c: Widget; x, y, btn: INTEGER);
 VAR msg: MouseMoveMsg;
 BEGIN
   IF (0 <= x) & (x < c.w) & (0 <= y) & (y < c.h) THEN
@@ -97,54 +126,70 @@ BEGIN
     hoveredWidget := NIL
   END;
 
-  msg.x := x; msg.y := y;
+  msg.x := x; msg.y := y; msg.btn := btn;
   c.handle(c, msg);
 
-  IF c.onMouseMove # NIL THEN c.onMouseMove(c, x, y) END
+  IF c.onMouseMove # NIL THEN c.onMouseMove(c, x, y, btn) END
 END WidgetOnMouseMove;
 
-PROCEDURE WidgetHandleMouseMove*(c: Widget; x, y: INTEGER);
+PROCEDURE WidgetHandleMouseMove*(c: Widget; x, y, btn: INTEGER);
 VAR p: Widget;
 BEGIN
   IF pressedWidget # NIL THEN
-    WidgetOnMouseMove(pressedWidget, x - pressedX, y - pressedY)
+    WidgetOnMouseMove(pressedWidget, x - pressedX, y - pressedY, btn)
   ELSE
     p := FindHoveredInList(c.body, x, y, FALSE);
     IF p # NIL THEN
-      WidgetHandleMouseMove(p, x - p.x, y - p.y)
+      WidgetHandleMouseMove(p, x - p.x, y - p.y, btn)
     ELSE
-      WidgetOnMouseMove(c, x, y)
+      WidgetOnMouseMove(c, x, y, btn)
     END
   END
 END WidgetHandleMouseMove;
 
-PROCEDURE WidgetOnMouseDown*(c: Widget; x, y: INTEGER);
+PROCEDURE Focus*(c: Widget);
+VAR get: GetFocusMsg;
+  lost: LostFocusMsg;
+BEGIN
+  IF c.focusable THEN
+    IF focusedWidget # NIL THEN
+      focusedWidget.focused := FALSE;
+      focusedWidget.handle(focusedWidget, lost)
+    END;
+    c.focused := TRUE;
+    focusedWidget := c;
+    focusedWidget.handle(focusedWidget, get)
+  END
+END Focus;
+
+PROCEDURE WidgetOnMouseDown*(c: Widget; x, y, btn: INTEGER);
 VAR msg: MouseDownMsg;
 BEGIN
   pressedWidget := c;
-  msg.x := x; msg.y := y;
+  Focus(c);
+  msg.x := x; msg.y := y; msg.btn := btn;
   c.handle(c, msg);
-  IF c.onMouseDown # NIL THEN c.onMouseDown(c, x, y) END
+  IF c.onMouseDown # NIL THEN c.onMouseDown(c, x, y, btn) END
 END WidgetOnMouseDown;
 
-PROCEDURE WidgetHandleMouseDown*(c: Widget; x, y: INTEGER);
+PROCEDURE WidgetHandleMouseDown*(c: Widget; x, y, btn: INTEGER);
 VAR p: Widget;
 BEGIN
   p := FindHoveredInList(c.body, x, y, TRUE);
   IF p # NIL THEN
-    WidgetHandleMouseDown(p, x - p.x, y - p.y)
+    WidgetHandleMouseDown(p, x - p.x, y - p.y, btn)
   ELSE
-    WidgetOnMouseDown(c, x, y)
+    WidgetOnMouseDown(c, x, y, btn)
   END
 END WidgetHandleMouseDown;
 
-PROCEDURE WidgetOnMouseUp*(c: Widget; x, y: INTEGER);
+PROCEDURE WidgetOnMouseUp*(c: Widget; x, y, btn: INTEGER);
 VAR msg: MouseUpMsg;
 BEGIN
   pressedWidget := NIL;
-  msg.x := x; msg.y := y;
+  msg.x := x; msg.y := y; msg.btn := btn;
   c.handle(c, msg);
-  IF c.onMouseUp # NIL THEN c.onMouseUp(c, x, y) END
+  IF c.onMouseUp # NIL THEN c.onMouseUp(c, x, y, btn) END
 END WidgetOnMouseUp;
 
 PROCEDURE WidgetOnClick*(c: Widget);
@@ -177,21 +222,23 @@ VAR p: Widget;
   cx, cy, cw, ch: INTEGER;
 BEGIN
   p := c.body;
-  WHILE p # NIL DO
-    x2 := x + p.x; y2 := y + p.y;
-    w2 := w - p.x; h2 := h - p.y;
-    (* !FIXME clip x2,y2,w2,h2 to not more than x,y,w,h*)
-    cx := x2; cy := y2; cw := p.w; ch := p.h;
-    IF cx + cw > x + w THEN cw := x + w - cx END;
-    IF cy + ch > y + h THEN ch := y + h - cy END;
-    IF cx < x THEN DEC(cw, x - cx); cx := x END;
-    IF cy < y THEN DEC(ch, y - cy); cy := y END;
-    G.SetClip(cx, cy, cw, ch);
-
-    DrawWidget(p, x2, y2, p.w, p.h);
-    p := p.next
-  END;
-  G.UnsetClip
+  IF p # NIL THEN
+    REPEAT
+      x2 := x + p.x; y2 := y + p.y;
+      w2 := w - p.x; h2 := h - p.y;
+
+      cx := x2; cy := y2; cw := p.w; ch := p.h;
+      IF cx + cw > x + w THEN cw := x + w - cx END;
+      IF cy + ch > y + h THEN ch := y + h - cy END;
+      IF cx < x THEN DEC(cw, x - cx); cx := x END;
+      IF cy < y THEN DEC(ch, y - cy); cy := y END;
+      G.SetClip(cx, cy, cw, ch);
+
+      DrawWidget(p, x2, y2, p.w, p.h);
+      p := p.next
+    UNTIL p = c.body;
+    G.UnsetClip
+  END
 END DrawBody;
 
 PROCEDURE SetBgColor*(c: Widget; color: G.Color);
@@ -202,15 +249,15 @@ PROCEDURE SetFgColor*(c: Widget; color: G.Color);
 BEGIN c.fgColor := color
 END SetFgColor;
 
-PROCEDURE SetOnMouseMove*(c: Widget; proc: PROCEDURE (c: Widget; x, y: INTEGER));
+PROCEDURE SetOnMouseMove*(c: Widget; proc: PROCEDURE (c: Widget; x, y, btn: INTEGER));
 BEGIN c.onMouseMove := proc
 END SetOnMouseMove;
 
-PROCEDURE SetOnMouseDown*(c: Widget; proc: PROCEDURE (c: Widget; x, y: INTEGER));
+PROCEDURE SetOnMouseDown*(c: Widget; proc: PROCEDURE (c: Widget; x, y, btn: INTEGER));
 BEGIN c.onMouseDown := proc
 END SetOnMouseDown;
 
-PROCEDURE SetOnMouseUp*(c: Widget; proc: PROCEDURE (c: Widget; x, y: INTEGER));
+PROCEDURE SetOnMouseUp*(c: Widget; proc: PROCEDURE (c: Widget; x, y, btn: INTEGER));
 BEGIN c.onMouseUp := proc
 END SetOnMouseUp;
 
@@ -220,27 +267,30 @@ END SetOnClick;
 
 PROCEDURE InitWidget*(c: Widget; w, h: INTEGER);
 BEGIN c.x := 0; c.y := 0; c.w := w; c.h := h;
+  c.focusable := FALSE; c.focused := FALSE;
+  c.hovered := FALSE; c.pressed := FALSE;
   G.MakeCol(c.bgColor, 180, 180, 180);
   G.MakeCol(c.fgColor, 0, 0, 0);
-  c.handle := WidgetHandler;
-  c.body := NIL; c.next := NIL
+  c.handle := WidgetHandler
 END InitWidget;
 
+PROCEDURE AppendToRing*(c: Widget; VAR ring: Widget);
+BEGIN
+  IF ring = NIL THEN
+    ring := c;
+    c.prev := c; c.next := c
+  ELSE
+    c.next := ring; c.prev := ring.prev;
+    ring.prev.next := c; ring.prev := c
+  END
+END AppendToRing;
+
 PROCEDURE Put*(c, where: Widget; x, y: INTEGER);
 VAR p: Widget;
 BEGIN
-  IF c # NIL THEN
+  IF (c # NIL) & (where # NIL) THEN
     c.x := x; c.y := y;
-    IF where # NIL THEN
-      c.next := NIL;
-      p := where.body;
-      IF p = NIL THEN
-        where.body := c
-      ELSE
-        WHILE p.next # NIL DO p := p.next END;
-        p.next := c
-      END
-    END
+    AppendToRing(c, where.body)
   END
 END Put;
 
@@ -283,7 +333,7 @@ END FormHandler;
 PROCEDURE InitForm*(c: Form; x, y, w, h: INTEGER);
 BEGIN InitPanel(c, NIL, x, y, w, h);
   c.handle := FormHandler;
-  c.next := forms; forms := c
+  AppendToRing(c, forms)
 END InitForm;
 
 PROCEDURE NewForm*(x, y, w, h: INTEGER): Form;
@@ -294,26 +344,26 @@ RETURN c END NewForm;
 (** Button **)
 
 PROCEDURE DrawButton*(c: Button; x, y, w, h: INTEGER);
-VAR cw, ch, tw, tx, ty: INTEGER;
+VAR fw, fh, tw, tx, ty: INTEGER;
   down: BOOLEAN;
   Z: G.Color;
 BEGIN
-  down := c(Button).pressed & c(Button).hovered;
+  down := c.pressed & c.hovered;
   G.FillRect(x, y, x + c.w - 1, y + c.h - 1, c.bgColor);
 
   ;G.MakeCol(Z, 255, 128, 0);
-  ;G.Line(x + c.h DIV 4, y + c.h DIV 2, x + c(Button).X, y + c(Button).Y, Z);
+  ;G.Line(x + c.h DIV 4, y + c.h DIV 2, x + c.X, y + c.Y, Z);
   ;G.MakeCol(Z, 215, 0, 0);
-  ;G.Line(x + c.h DIV 4, y + c.h DIV 2 + 1, x + c(Button).X, y + c(Button).Y + 1, Z);
+  ;G.Line(x + c.h DIV 4, y + c.h DIV 2 + 1, x + c.X, y + c.Y + 1, Z);
 
   G.Rect(x, y, x + c.w - 1, y + c.h - 1, c.fgColor);
   IF ~down THEN
     G.Rect(x, y, x + c.w - 2, y + c.h - 2, c.fgColor)
   END;
-  G.GetMonoFontSize(font, cw, ch);
-  tw := Strings.Length(c.caption) * cw;
+  G.GetMonoFontSize(font, fw, fh);
+  tw := Strings.Length(c.caption) * fw;
   tx := x + (c.w - tw) DIV 2;
-  ty := y + (c.h - ch) DIV 2;
+  ty := y + (c.h - fh) DIV 2;
   IF down THEN INC(tx); INC(ty) END;
   G.DrawString(c.caption, tx, ty, font, c.fgColor)
 END DrawButton;
@@ -332,7 +382,8 @@ BEGIN b := c(Button);
   ELSIF msg IS MouseMoveMsg THEN BMM(b, msg(MouseMoveMsg).x, msg(MouseMoveMsg).y)
   ELSIF msg IS MouseEnterMsg THEN b.hovered := TRUE
   ELSIF msg IS MouseLeaveMsg THEN b.hovered := FALSE
-  ELSIF msg IS MouseDownMsg THEN b.pressed := TRUE
+  ELSIF msg IS MouseDownMsg THEN
+    IF msg(MouseDownMsg).btn = 1 THEN b.pressed := TRUE END
   ELSIF msg IS MouseUpMsg THEN b.pressed := FALSE
   ELSE WidgetHandler(c, msg)
   END
@@ -342,8 +393,7 @@ PROCEDURE InitButton*(c: Button; where: Widget;
     x, y, w, h: INTEGER; caption: ARRAY OF CHAR);
 BEGIN InitWidget(c, w, h);
   Strings.Copy(caption, c.caption);
-  c.hovered := FALSE;
-  c.pressed := FALSE;
+  c.focusable := TRUE;
   c.handle := ButtonHandler;
   Put(c, where, x, y)
   ;c.X := 0; c.Y := 0;
@@ -354,16 +404,123 @@ VAR c: Button;
 BEGIN NEW(c); InitButton(c, where, x, y, w, h, caption)
 RETURN c END NewButton;
 
+(** Edit **)
+
+PROCEDURE DrawEdit*(c: Edit; x, y, w, h: INTEGER);
+VAR fw, fh, tw, tx, ty: INTEGER;
+  down: BOOLEAN;
+  red: G.Color;
+BEGIN
+  G.FillRect(x, y, x + c.w - 1, y + c.h - 1, c.bgColor);
+  G.GetMonoFontSize(font, fw, fh);
+  tw := Strings.Length(c.text) * fw;
+  tx := x + 2 - c.off;
+  ty := y + (c.h - fh) DIV 2;
+  G.DrawString(c.text, tx, ty, font, c.fgColor);
+  IF c.focused THEN
+    G.MakeCol(red, 250, 0, 0);
+    INC(tx, fw * c.pos - 1);
+    G.VLine(tx, ty, ty + fh - 1, red);
+    G.HLine(tx - 1, ty, tx + 1, red);
+    G.HLine(tx - 1, ty + fh - 1, tx + 1, red)
+  END;
+  G.Rect(x, y, x + c.w - 1, y + c.h - 1, c.fgColor)
+END DrawEdit;
+
+PROCEDURE EditOnMouseDown*(c: Edit; VAR msg: MouseDownMsg);
+VAR n, fw, fh: INTEGER;
+BEGIN
+  IF (msg.btn = 1) & (msg.x > 0) & (msg.x < c.w - 1) &
+     (msg.y > 0) & (msg.y < c.h - 1)
+  THEN
+    G.GetMonoFontSize(font, fw, fh);
+    n := (msg.x - 2 + fw DIV 2) DIV fw;
+    IF n < 0 THEN n := 0 ELSIF n > c.len THEN n := c.len END;
+    c.pos := n
+  END
+END EditOnMouseDown;
+
+PROCEDURE EditCheckOffset(c: Edit);
+VAR n, fw, fh: INTEGER;
+BEGIN
+  G.GetMonoFontSize(font, fw, fh);
+  n := c.pos * fw - c.off;
+  IF c.len * fw <= c.w - 4 THEN c.off := 0
+  ELSIF n < 0 THEN c.off := c.pos * fw
+  ELSIF n >= c.w - 4 THEN c.off := c.pos * fw - c.w + 4
+  ELSIF c.len * fw - c.off <= c.w - 4 THEN c.off := c.len * fw - c.w + 4
+  END
+END EditCheckOffset;
+
+PROCEDURE EditOnChar*(c: Edit; VAR msg: CharMsg);
+VAR i: INTEGER;
+BEGIN
+  IF msg.key = G.kBackspace THEN
+    IF c.pos > 0 THEN
+      Strings.Delete(c.text, c.pos - 1, 1);
+      DEC(c.len); DEC(c.pos)
+    END
+  ELSIF msg.key = G.kDel THEN
+    IF c.pos < c.len THEN
+      Strings.Delete(c.text, c.pos, 1);
+      DEC(c.len)
+    END
+  ELSIF msg.ch < ' ' THEN
+    IF msg.key = G.kLeft THEN DEC(c.pos)
+    ELSIF msg.key = G.kRight THEN INC(c.pos)
+    ELSIF msg.key = G.kHome THEN c.pos := 0
+    ELSIF msg.key = G.kEnd THEN c.pos := c.len
+    END;
+    IF c.pos < 0 THEN c.pos := 0 ELSIF c.pos > c.len THEN c.pos := c.len END
+  ELSIF c.len < LEN(c.text) - 1 THEN
+    c.text[c.len + 1] := 0X;
+    i := c.len;
+    WHILE i > c.pos DO
+      c.text[i] := c.text[i - 1];
+      DEC(i)
+    END;
+    c.text[c.pos] := msg.ch;
+    INC(c.len); INC(c.pos)
+  END;
+  EditCheckOffset(c)
+END EditOnChar;
+
+PROCEDURE EditHandler*(c: Widget; VAR msg: Message);
+VAR e: Edit;
+BEGIN e := c(Edit);
+  IF msg IS DrawMsg THEN
+    DrawEdit(e, msg(DrawMsg).x, msg(DrawMsg).y,
+        msg(DrawMsg).w, msg(DrawMsg).h)
+  ELSIF msg IS MouseDownMsg THEN EditOnMouseDown(e, msg(MouseDownMsg))
+  ELSIF msg IS CharMsg THEN EditOnChar(e, msg(CharMsg))
+  ELSE WidgetHandler(c, msg)
+  END
+END EditHandler;
+
+PROCEDURE InitEdit*(c: Edit; where: Widget; x, y, w, h: INTEGER);
+BEGIN InitWidget(c, w, h);
+  c.focusable := TRUE;
+  G.MakeCol(c.bgColor, 255, 255, 255);
+  c.text := 'Привет'; c.len := 6; c.pos := 2; c.off := 0;
+  c.handle := EditHandler;
+  Put(c, where, x, y)
+END InitEdit;
+
+PROCEDURE NewEdit*(where: Widget; x, y, w, h: INTEGER): Edit;
+VAR c: Edit;
+BEGIN NEW(c); InitEdit(c, where, x, y, w, h)
+RETURN c END NewEdit;
+
 (** General **)
 
 PROCEDURE DrawAll*;
 VAR c: Widget;
 BEGIN
   c := forms;
-  WHILE c # NIL DO
+  REPEAT
     DrawForm(c(Form));
     c := c.next
-  END;
+  UNTIL c = forms;
   G.Flip
 END DrawAll;
 
@@ -372,7 +529,7 @@ VAR c: Widget;
 BEGIN
   c := FindHoveredInList(forms, e.x, e.y, FALSE);
   IF c # NIL THEN
-    WidgetHandleMouseMove(c, e.x - c.x, e.y - c.y)
+    WidgetHandleMouseMove(c, e.x - c.x, e.y - c.y, e.button)
   END
 END HandleMouseMove;
 
@@ -382,7 +539,7 @@ BEGIN
   pressedX := 0; pressedY := 0;
   c := FindHoveredInList(forms, e.x, e.y, TRUE);
   IF c # NIL THEN
-    WidgetHandleMouseDown(c, e.x - c.x, e.y - c.y)
+    WidgetHandleMouseDown(c, e.x - c.x, e.y - c.y, e.button)
   END
 END HandleMouseDown;
 
@@ -392,19 +549,50 @@ BEGIN
   IF pressedWidget # NIL THEN
     c := pressedWidget;
     IF ~c.hovered THEN c := NIL END;
-    WidgetOnMouseUp(pressedWidget, e.x - pressedX, e.y - pressedY);
-    IF c # NIL THEN
+    WidgetOnMouseUp(pressedWidget, e.x - pressedX, e.y - pressedY, e.button);
+    IF (c # NIL) & (e.button = 1) THEN
       WidgetOnClick(c)
     END
   END
 END HandleMouseUp;
 
+PROCEDURE HandleKeyDown(VAR e: G.Event);
+VAR msg: KeyDownMsg;
+BEGIN
+  IF focusedWidget # NIL THEN
+    msg.key := e.key;
+    focusedWidget.handle(focusedWidget, msg)
+  END
+END HandleKeyDown;
+
+PROCEDURE HandleKeyUp(VAR e: G.Event);
+VAR msg: KeyUpMsg;
+BEGIN
+  IF focusedWidget # NIL THEN
+    msg.key := e.key;
+    focusedWidget.handle(focusedWidget, msg)
+  END
+END HandleKeyUp;
+
+PROCEDURE HandleChar(VAR e: G.Event);
+VAR msg: CharMsg;
+BEGIN
+  IF focusedWidget # NIL THEN
+    msg.key := e.key; msg.ch := e.ch;
+    msg.mod := e.mod; msg.repeat := e.repeat;
+    focusedWidget.handle(focusedWidget, msg)
+  END
+END HandleChar;
+
 PROCEDURE HandleEvent(VAR e: G.Event);
 BEGIN
   IF e.type = G.quit THEN quit := TRUE
   ELSIF e.type = G.mouseMove THEN HandleMouseMove(e)
   ELSIF e.type = G.mouseDown THEN HandleMouseDown(e)
-  ELSIF e.type = G.mouseUp THEN HandleMouseUp(e)
+  ELSIF e.type = G.mouseUp   THEN HandleMouseUp(e)
+  ELSIF e.type = G.keyDown   THEN HandleKeyDown(e)
+  ELSIF e.type = G.keyUp     THEN HandleKeyUp(e)
+  ELSIF e.type = G.char      THEN HandleChar(e)
   END
 END HandleEvent;