Focus works, hot-keys on buttons, cursor positioning, Window repaint

This commit is contained in:
Miguel de Icaza
2018-01-04 21:35:33 -05:00
parent 59f73e587a
commit 27a8e1ca9d
5 changed files with 327 additions and 272 deletions

73
Core.cs
View File

@@ -16,7 +16,7 @@ namespace Terminal {
public class Responder {
public virtual bool CanFocus { get; set; }
public bool HasFocus { get; internal set; }
public virtual bool HasFocus { get; internal set; }
// Key handling
/// <summary>
@@ -198,6 +198,14 @@ namespace Terminal {
CanFocus = true;
}
public void Add (params View [] views)
{
if (views == null)
return;
foreach (var view in views)
Add (view);
}
/// <summary>
/// Removes all the widgets from this container.
/// </summary>
@@ -332,6 +340,16 @@ namespace Terminal {
Move (frame.X, frame.Y);
}
public override bool HasFocus {
get {
return base.HasFocus;
}
internal set {
if (base.HasFocus != value)
SetNeedsDisplay ();
base.HasFocus = value;
}
}
/// <summary>
/// Returns the currently focused view inside this view, or null if nothing is focused.
/// </summary>
@@ -403,13 +421,12 @@ namespace Terminal {
if (c == null)
throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
if (focused != null)
if (focused != null)
focused.HasFocus = false;
focused = view;
view.HasFocus = true;
if (view != null)
view.EnsureFocus ();
focused.PositionCursor ();
focused.HasFocus = true;
focused.EnsureFocus ();
}
public override bool ProcessKey (KeyEvent kb)
@@ -420,6 +437,25 @@ namespace Terminal {
return false;
}
public override bool ProcessHotKey (KeyEvent kb)
{
if (subviews == null || subviews.Count == 0)
return false;
foreach (var view in subviews)
if (view.ProcessHotKey (kb))
return true;
return false;
}
public override bool ProcessColdKey (KeyEvent kb)
{
if (subviews == null || subviews.Count == 0)
return false;
foreach (var view in subviews)
if (view.ProcessHotKey (kb))
return true;
return false;
}
/// <summary>
/// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing.
@@ -677,19 +713,21 @@ namespace Terminal {
public override void Redraw (Rect bounds)
{
Driver.SetAttribute (Colors.Base.Normal);
DrawFrame ();
if (HasFocus)
if (NeedDisplay) {
Driver.SetAttribute (Colors.Base.Normal);
DrawFrame ();
if (HasFocus)
Driver.SetAttribute (Colors.Dialog.Normal);
var width = Frame.Width;
if (Title != null && width > 4) {
Move (1, 0);
Driver.AddCh (' ');
var str = Title.Length > width ? Title.Substring (0, width - 4) : Title;
Driver.AddStr (str);
Driver.AddCh (' ');
}
Driver.SetAttribute (Colors.Dialog.Normal);
var width = Frame.Width;
if (Title != null && width > 4) {
Move (1, 0);
Driver.AddCh (' ');
var str = Title.Length > width ? Title.Substring (0, width - 4) : Title;
Driver.AddStr (str);
Driver.AddCh (' ');
}
Driver.SetAttribute (Colors.Dialog.Normal);
contentView.Redraw (contentView.Bounds);
}
}
@@ -848,6 +886,7 @@ namespace Terminal {
return;
if (state.Toplevel.NeedDisplay || state.Toplevel.childNeedsDisplay) {
state.Toplevel.Redraw (state.Toplevel.Bounds);
state.Toplevel.PositionCursor ();
Driver.Refresh ();
}
}

202
Event.cs
View File

@@ -1,114 +1,114 @@
namespace Terminal {
/// <summary>
/// The Key enumeration contains special encoding for some keys, but can also
/// encode all the unicode values that can be passed.
/// </summary>
/// <remarks>
/// <para>
/// If the SpecialMask is set, then the value is that of the special mask,
/// otherwise, the value is the one of the lower bits (as extracted by CharMask)
/// </para>
/// <para>
/// Control keys are the values between 1 and 26 corresponding to Control-A to Control-Z
/// </para>
/// </remarks>
public enum Key : uint {
CharMask = 0xfffff,
SpecialMask = 0xfff00000,
ControlA = 1,
ControlB,
ControlC,
ControlD,
ControlE,
ControlF,
ControlG,
ControlH,
ControlI,
Tab = ControlI,
ControlJ,
ControlK,
ControlL,
ControlM,
ControlN,
ControlO,
ControlP,
ControlQ,
ControlR,
ControlS,
ControlT,
ControlU,
ControlV,
ControlW,
ControlX,
ControlY,
ControlZ,
Esc = 27,
Space = 32,
Delete = 127,
/// <summary>
/// The Key enumeration contains special encoding for some keys, but can also
/// encode all the unicode values that can be passed.
/// </summary>
/// <remarks>
/// <para>
/// If the SpecialMask is set, then the value is that of the special mask,
/// otherwise, the value is the one of the lower bits (as extracted by CharMask)
/// </para>
/// <para>
/// Control keys are the values between 1 and 26 corresponding to Control-A to Control-Z
/// </para>
/// </remarks>
public enum Key : uint {
CharMask = 0xfffff,
SpecialMask = 0xfff00000,
ControlA = 1,
ControlB,
ControlC,
ControlD,
ControlE,
ControlF,
ControlG,
ControlH,
ControlI,
Tab = ControlI,
ControlJ,
ControlK,
ControlL,
ControlM,
ControlN,
ControlO,
ControlP,
ControlQ,
ControlR,
ControlS,
ControlT,
ControlU,
ControlV,
ControlW,
ControlX,
ControlY,
ControlZ,
Esc = 27,
Space = 32,
Delete = 127,
AltMask = 0x80000000,
AltMask = 0x80000000,
Backspace = 0x100000,
CursorUp,
CursorDown,
CursorLeft,
CursorRight,
PageUp,
PageDown,
Home,
End,
DeleteChar,
InsertChar,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
BackTab,
Unknown
}
Backspace = 0x100000,
CursorUp,
CursorDown,
CursorLeft,
CursorRight,
PageUp,
PageDown,
Home,
End,
DeleteChar,
InsertChar,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
BackTab,
Unknown
}
public struct KeyEvent {
public Key Key;
public int KeyValue => (int)Key;
public bool IsAlt => (Key & Key.AltMask) != 0;
public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26);
public struct KeyEvent {
public Key Key;
public int KeyValue => (int)Key;
public bool IsAlt => (Key & Key.AltMask) != 0;
public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26);
public KeyEvent (Key k)
{
Key = k;
}
}
public KeyEvent (Key k)
{
Key = k;
}
}
public class Event {
public class Key : Event {
public int Code { get; private set; }
public bool Alt { get; private set; }
public Key (int code)
{
Code = code;
}
}
public class Event {
public class Key : Event {
public int Code { get; private set; }
public bool Alt { get; private set; }
public Key (int code)
{
Code = code;
}
}
public class Mouse : Event {
}
public class Mouse : Event {
}
public static Event CreateMouseEvent ()
{
return new Mouse ();
}
public static Event CreateMouseEvent ()
{
return new Mouse ();
}
public static Event CreateKeyEvent (int code)
{
return new Key (code);
}
public static Event CreateKeyEvent (int code)
{
return new Key (code);
}
}
}
}

View File

@@ -3,166 +3,166 @@ using System.Collections.Generic;
using System.Linq;
namespace Terminal {
/// <summary>
/// Button view
/// </summary>
/// <remarks>
/// Provides a button that can be clicked, or pressed with
/// the enter key and processes hotkeys (the first uppercase
/// letter in the button becomes the hotkey).
/// </remarks>
public class Button : View {
string text;
string shown_text;
char hot_key;
int hot_pos = -1;
bool is_default;
/// <summary>
/// Button view
/// </summary>
/// <remarks>
/// Provides a button that can be clicked, or pressed with
/// the enter key and processes hotkeys (the first uppercase
/// letter in the button becomes the hotkey).
/// </remarks>
public class Button : View {
string text;
string shown_text;
char hot_key;
int hot_pos = -1;
bool is_default;
/// <summary>
/// Clicked event, raised when the button is clicked.
/// </summary>
/// <remarks>
/// Client code can hook up to this event, it is
/// raised when the button is activated either with
/// the mouse or the keyboard.
/// </remarks>
public event EventHandler Clicked;
/// <summary>
/// Clicked event, raised when the button is clicked.
/// </summary>
/// <remarks>
/// Client code can hook up to this event, it is
/// raised when the button is activated either with
/// the mouse or the keyboard.
/// </remarks>
public event EventHandler Clicked;
/// <summary>
/// Public constructor, creates a button based on
/// the given text at position 0,0
/// </summary>
/// <remarks>
/// The size of the button is computed based on the
/// text length. This button is not a default button.
/// </remarks>
public Button (string s) : this (0, 0, s) { }
/// <summary>
/// Public constructor, creates a button based on
/// the given text at position 0,0
/// </summary>
/// <remarks>
/// The size of the button is computed based on the
/// text length. This button is not a default button.
/// </remarks>
public Button (string s) : this (0, 0, s) { }
/// <summary>
/// Public constructor, creates a button based on
/// the given text.
/// </summary>
/// <remarks>
/// If the value for is_default is true, a special
/// decoration is used, and the enter key on a
/// dialog would implicitly activate this button.
/// </remarks>
public Button (string s, bool is_default) : this (0, 0, s, is_default) { }
/// <summary>
/// Public constructor, creates a button based on
/// the given text.
/// </summary>
/// <remarks>
/// If the value for is_default is true, a special
/// decoration is used, and the enter key on a
/// dialog would implicitly activate this button.
/// </remarks>
public Button (string s, bool is_default) : this (0, 0, s, is_default) { }
/// <summary>
/// Public constructor, creates a button based on
/// the given text at the given position.
/// </summary>
/// <remarks>
/// The size of the button is computed based on the
/// text length. This button is not a default button.
/// </remarks>
public Button (int x, int y, string s) : this (x, y, s, false) { }
/// <summary>
/// Public constructor, creates a button based on
/// the given text at the given position.
/// </summary>
/// <remarks>
/// The size of the button is computed based on the
/// text length. This button is not a default button.
/// </remarks>
public Button (int x, int y, string s) : this (x, y, s, false) { }
/// <summary>
/// The text displayed by this widget.
/// </summary>
public string Text {
get {
return text;
}
/// <summary>
/// The text displayed by this widget.
/// </summary>
public string Text {
get {
return text;
}
set {
text = value;
if (is_default)
shown_text = "[< " + value + " >]";
else
shown_text = "[ " + value + " ]";
set {
text = value;
if (is_default)
shown_text = "[< " + value + " >]";
else
shown_text = "[ " + value + " ]";
hot_pos = -1;
hot_key = (char)0;
int i = 0;
foreach (char c in shown_text) {
if (Char.IsUpper (c)) {
hot_key = c;
hot_pos = i;
break;
}
i++;
}
}
}
hot_pos = -1;
hot_key = (char)0;
int i = 0;
foreach (char c in shown_text) {
if (Char.IsUpper (c)) {
hot_key = c;
hot_pos = i;
break;
}
i++;
}
}
}
/// <summary>
/// Public constructor, creates a button based on
/// the given text at the given position.
/// </summary>
/// <remarks>
/// If the value for is_default is true, a special
/// decoration is used, and the enter key on a
/// dialog would implicitly activate this button.
/// </remarks>
public Button (int x, int y, string s, bool is_default)
: base (new Rect (x, y, s.Length + 4 + (is_default ? 2 : 0), 1))
{
CanFocus = true;
/// <summary>
/// Public constructor, creates a button based on
/// the given text at the given position.
/// </summary>
/// <remarks>
/// If the value for is_default is true, a special
/// decoration is used, and the enter key on a
/// dialog would implicitly activate this button.
/// </remarks>
public Button (int x, int y, string s, bool is_default)
: base (new Rect (x, y, s.Length + 4 + (is_default ? 2 : 0), 1))
{
CanFocus = true;
this.is_default = is_default;
Text = s;
}
this.is_default = is_default;
Text = s;
}
public override void Redraw (Rect region)
{
Driver.SetAttribute (HasFocus ? Colors.Base.Focus : Colors.Base.Normal);
Move (0, 0);
Driver.AddStr (shown_text);
public override void Redraw (Rect region)
{
Driver.SetAttribute (HasFocus ? Colors.Base.Focus : Colors.Base.Normal);
Move (0, 0);
Driver.AddStr (shown_text);
if (hot_pos != -1) {
Move (hot_pos, 0);
Driver.SetAttribute (HasFocus ? Colors.Base.HotFocus: Colors.Base.HotNormal);
Driver.AddCh (hot_key);
}
}
if (hot_pos != -1) {
Move (hot_pos, 0);
Driver.SetAttribute (HasFocus ? Colors.Base.HotFocus : Colors.Base.HotNormal);
Driver.AddCh (hot_key);
}
}
public override void PositionCursor ()
{
Move (hot_pos, 0);
}
public override void PositionCursor ()
{
Move (hot_pos, 0);
}
bool CheckKey (KeyEvent key)
{
if (Char.ToUpper ((char)key.KeyValue) == hot_key) {
this.SetFocus (this);
if (Clicked != null)
Clicked (this, EventArgs.Empty);
return true;
}
return false;
}
bool CheckKey (KeyEvent key)
{
if (Char.ToUpper ((char)key.KeyValue) == hot_key) {
this.SuperView.SetFocus (this);
if (Clicked != null)
Clicked (this, EventArgs.Empty);
return true;
}
return false;
}
public override bool ProcessHotKey (KeyEvent kb)
{
if (kb.IsAlt)
return CheckKey (kb);
public override bool ProcessHotKey (KeyEvent kb)
{
if (kb.IsAlt)
return CheckKey (kb);
return false;
}
return false;
}
public override bool ProcessColdKey (KeyEvent kb)
{
if (is_default && kb.KeyValue == '\n') {
if (Clicked != null)
Clicked (this, EventArgs.Empty);
return true;
}
return CheckKey (kb);
}
public override bool ProcessColdKey (KeyEvent kb)
{
if (is_default && kb.KeyValue == '\n') {
if (Clicked != null)
Clicked (this, EventArgs.Empty);
return true;
}
return CheckKey (kb);
}
public override bool ProcessKey (KeyEvent kb)
{
var c = kb.KeyValue;
if (c == '\n' || c == ' ' || Char.ToUpper ((char)c) == hot_key) {
if (Clicked != null)
Clicked (this, EventArgs.Empty);
return true;
}
return false;
}
public override bool ProcessKey (KeyEvent kb)
{
var c = kb.KeyValue;
if (c == '\n' || c == ' ' || Char.ToUpper ((char)c) == hot_key) {
if (Clicked != null)
Clicked (this, EventArgs.Empty);
return true;
}
return false;
}
#if false
public override void ProcessMouse (Curses.MouseEvent ev)
@@ -175,5 +175,5 @@ namespace Terminal {
}
}
#endif
}
}
}

View File

@@ -105,7 +105,7 @@ namespace Terminal {
if (p < text.Length) {
Driver.AddCh (Secret ? '*' : text [p]);
} else
Driver.AddCh ('_');
Driver.AddCh (' ');
}
PositionCursor ();
}

34
demo.cs
View File

@@ -1,19 +1,35 @@
using Terminal;
class Demo {
static void ShowTextAlignments (View container)
{
container.Add (
new Label (new Rect (0, 0, 40, 3), "1-Hello world, how are you doing today") { TextAlignment = TextAlignment.Left },
new Label (new Rect (0, 4, 40, 3), "2-Hello world, how are you doing today") { TextAlignment = TextAlignment.Right },
new Label (new Rect (0, 8, 40, 3), "3-Hello world, how are you doing today") { TextAlignment = TextAlignment.Centered },
new Label (new Rect (0, 12, 40, 3), "4-Hello world, how are you doing today") { TextAlignment = TextAlignment.Justified });
}
static void ShowEntries (View container)
{
container.Add (
new Label (3, 2, "Login: "),
new TextField (14, 2, 40, ""),
new Label (3, 4, "Password: "),
new TextField (14, 4, 40, "") { Secret = true },
new Button (3, 6, "Ok"),
new Button (10, 6, "Cancel")
);
}
static void Main ()
{
Application.Init ();
var top = Application.Top;
var win = new Window (new Rect (0, 0, 80, 24), "Hello") {
new Label (new Rect (0, 0, 40, 3), "1-Hello world, how are you doing today") { TextAlignment = TextAlignment.Left },
new Label (new Rect (0, 4, 40, 3), "2-Hello world, how are you doing today") { TextAlignment = TextAlignment.Right},
new Label (new Rect (0, 8, 40, 3), "3-Hello world, how are you doing today") { TextAlignment = TextAlignment.Centered },
new Label (new Rect (0, 12, 40, 3), "4-Hello world, how are you doing today") { TextAlignment = TextAlignment.Justified},
//new Button (3, 16, "Ok"),
new Label (3, 14, "Login: "),
new TextField (10, 14, 40, ""),
};
var win = new Window (new Rect (0, 0, 80, 24), "Hello");
ShowEntries (win);
// ShowTextAlignments (win);
top.Add (win);
Application.Run ();
}