Some work on display flags

This commit is contained in:
Miguel de Icaza
2018-01-03 22:17:30 -05:00
parent 4869ca42b1
commit 59f73e587a
7 changed files with 1491 additions and 1428 deletions

1472
Core.cs

File diff suppressed because it is too large Load Diff

578
Driver.cs
View File

@@ -5,328 +5,328 @@ using Unix.Terminal;
namespace Terminal {
/// <summary>
/// Basic colors that can be used to set the foreground and background colors in console applications. These can only be
/// </summary>
public enum Color {
Black,
Blue,
Green,
Cyan,
Red,
Magenta,
Brown,
Gray,
DarkGray,
BrightBlue,
BrightGreen,
BrighCyan,
BrightRed,
BrightMagenta,
BrightYellow,
White
}
/// <summary>
/// Basic colors that can be used to set the foreground and background colors in console applications. These can only be
/// </summary>
public enum Color {
Black,
Blue,
Green,
Cyan,
Red,
Magenta,
Brown,
Gray,
DarkGray,
BrightBlue,
BrightGreen,
BrighCyan,
BrightRed,
BrightMagenta,
BrightYellow,
White
}
public struct Attribute {
internal int value;
public Attribute (int v)
{
value = v;
}
public struct Attribute {
internal int value;
public Attribute (int v)
{
value = v;
}
public static implicit operator int (Attribute c) => c.value;
public static implicit operator Attribute (int v) => new Attribute (v);
}
public static implicit operator int (Attribute c) => c.value;
public static implicit operator Attribute (int v) => new Attribute (v);
}
public class ColorScheme {
public Attribute Normal;
public Attribute Focus;
public Attribute HotNormal;
public Attribute HotFocus;
public Attribute Marked => HotNormal;
public Attribute MarkedSelected => HotFocus;
}
public class ColorScheme {
public Attribute Normal;
public Attribute Focus;
public Attribute HotNormal;
public Attribute HotFocus;
public Attribute Marked => HotNormal;
public Attribute MarkedSelected => HotFocus;
}
public static class Colors {
public static ColorScheme Base, Dialog, Menu, Error;
public static class Colors {
public static ColorScheme Base, Dialog, Menu, Error;
}
}
public abstract class ConsoleDriver {
public abstract int Cols { get; }
public abstract int Rows { get; }
public abstract void Init (Action terminalResized);
public abstract void Move (int col, int row);
public abstract void AddCh (int ch);
public abstract void AddStr (string str);
public abstract void PrepareToRun (MainLoop mainLoop, Responder target);
public abstract void Refresh ();
public abstract void End ();
public abstract void RedrawTop ();
public abstract void SetAttribute (Attribute c);
public abstract class ConsoleDriver {
public abstract int Cols { get; }
public abstract int Rows { get; }
public abstract void Init (Action terminalResized);
public abstract void Move (int col, int row);
public abstract void AddCh (int ch);
public abstract void AddStr (string str);
public abstract void PrepareToRun (MainLoop mainLoop, Responder target);
public abstract void Refresh ();
public abstract void End ();
public abstract void RedrawTop ();
public abstract void SetAttribute (Attribute c);
// Set Colors from limit sets of colors
public abstract void SetColors (ConsoleColor foreground, ConsoleColor background);
// Set Colors from limit sets of colors
public abstract void SetColors (ConsoleColor foreground, ConsoleColor background);
// Advanced uses - set colors to any pre-set pairs, you would need to init_color
// that independently with the R, G, B values.
public abstract void SetColors (short foreColorId, short backgroundColorId);
// Advanced uses - set colors to any pre-set pairs, you would need to init_color
// that independently with the R, G, B values.
public abstract void SetColors (short foreColorId, short backgroundColorId);
public abstract void DrawFrame (Rect region, bool fill);
public abstract void DrawFrame (Rect region, bool fill);
Rect clip;
public Rect Clip {
get => clip;
set => this.clip = value;
}
}
Rect clip;
public Rect Clip {
get => clip;
set => this.clip = value;
}
}
public class CursesDriver : ConsoleDriver {
Action terminalResized;
public class CursesDriver : ConsoleDriver {
Action terminalResized;
public override int Cols => Curses.Cols;
public override int Rows => Curses.Lines;
public override int Cols => Curses.Cols;
public override int Rows => Curses.Lines;
// Current row, and current col, tracked by Move/AddCh only
int ccol, crow;
bool needMove;
public override void Move (int col, int row)
{
ccol = col;
crow = row;
// Current row, and current col, tracked by Move/AddCh only
int ccol, crow;
bool needMove;
public override void Move (int col, int row)
{
ccol = col;
crow = row;
if (Clip.Contains (col, row)) {
Curses.move (row, col);
needMove = false;
} else {
Curses.move (Clip.Y, Clip.X);
needMove = true;
}
}
if (Clip.Contains (col, row)) {
Curses.move (row, col);
needMove = false;
} else {
Curses.move (Clip.Y, Clip.X);
needMove = true;
}
}
public override void AddCh (int ch)
{
if (Clip.Contains (ccol, crow)) {
if (needMove) {
Curses.move (crow, ccol);
needMove = false;
}
Curses.addch (ch);
} else
needMove = true;
ccol++;
}
public override void AddCh (int ch)
{
if (Clip.Contains (ccol, crow)) {
if (needMove) {
Curses.move (crow, ccol);
needMove = false;
}
Curses.addch (ch);
} else
needMove = true;
ccol++;
}
public override void AddStr (string str)
{
// TODO; optimize this to determine if the str fits in the clip region, and if so, use Curses.addstr directly
foreach (var c in str)
AddCh ((int)c);
}
public override void AddStr (string str)
{
// TODO; optimize this to determine if the str fits in the clip region, and if so, use Curses.addstr directly
foreach (var c in str)
AddCh ((int)c);
}
public override void Refresh () => Curses.refresh ();
public override void End () => Curses.endwin ();
public override void RedrawTop () => window.redrawwin ();
public override void SetAttribute (Attribute c) => Curses.attrset (c.value);
public Curses.Window window;
public override void Refresh () => Curses.refresh ();
public override void End () => Curses.endwin ();
public override void RedrawTop () => window.redrawwin ();
public override void SetAttribute (Attribute c) => Curses.attrset (c.value);
public Curses.Window window;
static short last_color_pair = 16;
static Attribute MakeColor (short f, short b)
{
Curses.InitColorPair (++last_color_pair, f, b);
return new Attribute () { value = Curses.ColorPair (last_color_pair) };
}
static short last_color_pair = 16;
static Attribute MakeColor (short f, short b)
{
Curses.InitColorPair (++last_color_pair, f, b);
return new Attribute () { value = Curses.ColorPair (last_color_pair) };
}
int [,] colorPairs = new int [16, 16];
int [,] colorPairs = new int [16, 16];
public override void SetColors (ConsoleColor foreground, ConsoleColor background)
{
int f = (short)foreground;
int b = (short)background;
var v = colorPairs [f, b];
if ((v & 0x10000) == 0) {
b = b & 0x7;
bool bold = (f & 0x8) != 0;
f = f & 0x7;
public override void SetColors (ConsoleColor foreground, ConsoleColor background)
{
int f = (short)foreground;
int b = (short)background;
var v = colorPairs [f, b];
if ((v & 0x10000) == 0) {
b = b & 0x7;
bool bold = (f & 0x8) != 0;
f = f & 0x7;
v = MakeColor ((short)f, (short)b) | (bold ? Curses.A_BOLD : 0);
colorPairs [(int)foreground, (int)background] = v | 0x1000;
}
SetAttribute (v & 0xffff);
}
v = MakeColor ((short)f, (short)b) | (bold ? Curses.A_BOLD : 0);
colorPairs [(int)foreground, (int)background] = v | 0x1000;
}
SetAttribute (v & 0xffff);
}
Dictionary<int, int> rawPairs = new Dictionary<int, int> ();
public override void SetColors (short foreColorId, short backgroundColorId)
{
int key = (((ushort)foreColorId << 16)) | (ushort)backgroundColorId;
if (!rawPairs.TryGetValue (key, out var v)) {
v = MakeColor (foreColorId, backgroundColorId);
rawPairs [key] = v;
}
SetAttribute (v);
}
Dictionary<int, int> rawPairs = new Dictionary<int, int> ();
public override void SetColors (short foreColorId, short backgroundColorId)
{
int key = (((ushort)foreColorId << 16)) | (ushort)backgroundColorId;
if (!rawPairs.TryGetValue (key, out var v)) {
v = MakeColor (foreColorId, backgroundColorId);
rawPairs [key] = v;
}
SetAttribute (v);
}
static Key MapCursesKey (int cursesKey)
{
switch (cursesKey) {
case Curses.KeyF1: return Key.F1;
case Curses.KeyF2: return Key.F2;
case Curses.KeyF3: return Key.F3;
case Curses.KeyF4: return Key.F4;
case Curses.KeyF5: return Key.F5;
case Curses.KeyF6: return Key.F6;
case Curses.KeyF7: return Key.F7;
case Curses.KeyF8: return Key.F8;
case Curses.KeyF9: return Key.F9;
case Curses.KeyF10: return Key.F10;
case Curses.KeyUp: return Key.CursorUp;
case Curses.KeyDown: return Key.CursorDown;
case Curses.KeyLeft: return Key.CursorLeft;
case Curses.KeyRight: return Key.CursorRight;
case Curses.KeyHome: return Key.Home;
case Curses.KeyEnd: return Key.End;
case Curses.KeyNPage: return Key.PageDown;
case Curses.KeyPPage: return Key.PageUp;
case Curses.KeyDeleteChar: return Key.DeleteChar;
case Curses.KeyInsertChar: return Key.InsertChar;
case Curses.KeyBackTab: return Key.BackTab;
default: return Key.Unknown;
}
}
static Key MapCursesKey (int cursesKey)
{
switch (cursesKey) {
case Curses.KeyF1: return Key.F1;
case Curses.KeyF2: return Key.F2;
case Curses.KeyF3: return Key.F3;
case Curses.KeyF4: return Key.F4;
case Curses.KeyF5: return Key.F5;
case Curses.KeyF6: return Key.F6;
case Curses.KeyF7: return Key.F7;
case Curses.KeyF8: return Key.F8;
case Curses.KeyF9: return Key.F9;
case Curses.KeyF10: return Key.F10;
case Curses.KeyUp: return Key.CursorUp;
case Curses.KeyDown: return Key.CursorDown;
case Curses.KeyLeft: return Key.CursorLeft;
case Curses.KeyRight: return Key.CursorRight;
case Curses.KeyHome: return Key.Home;
case Curses.KeyEnd: return Key.End;
case Curses.KeyNPage: return Key.PageDown;
case Curses.KeyPPage: return Key.PageUp;
case Curses.KeyDeleteChar: return Key.DeleteChar;
case Curses.KeyInsertChar: return Key.InsertChar;
case Curses.KeyBackTab: return Key.BackTab;
default: return Key.Unknown;
}
}
void ProcessInput (Responder handler)
{
int wch;
var code = Curses.get_wch (out wch);
if (code == Curses.KEY_CODE_YES) {
if (wch == Curses.KeyResize) {
if (Curses.CheckWinChange ()) {
terminalResized ();
return;
}
}
if (code == Curses.KeyMouse) {
// TODO
// Curses.MouseEvent ev;
// Curses.getmouse (out ev);
// handler.HandleMouse ();
return;
}
handler.ProcessKey (new KeyEvent (MapCursesKey (wch)));
return;
}
void ProcessInput (Responder handler)
{
int wch;
var code = Curses.get_wch (out wch);
if (code == Curses.KEY_CODE_YES) {
if (wch == Curses.KeyResize) {
if (Curses.CheckWinChange ()) {
terminalResized ();
return;
}
}
if (code == Curses.KeyMouse) {
// TODO
// Curses.MouseEvent ev;
// Curses.getmouse (out ev);
// handler.HandleMouse ();
return;
}
handler.ProcessKey (new KeyEvent (MapCursesKey (wch)));
return;
}
// Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter.
if (wch == 27) {
Curses.timeout (100);
// Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter.
if (wch == 27) {
Curses.timeout (100);
code = Curses.get_wch (out wch);
if (code == Curses.KEY_CODE_YES)
handler.ProcessKey (new KeyEvent (Key.AltMask | MapCursesKey (wch)));
if (code == 0)
handler.ProcessKey (new KeyEvent (Key.AltMask | (Key)wch));
} else
handler.ProcessKey (new KeyEvent ((Key)wch));
}
code = Curses.get_wch (out wch);
if (code == Curses.KEY_CODE_YES)
handler.ProcessKey (new KeyEvent (Key.AltMask | MapCursesKey (wch)));
if (code == 0)
handler.ProcessKey (new KeyEvent (Key.AltMask | (Key)wch));
} else
handler.ProcessKey (new KeyEvent ((Key)wch));
}
public override void PrepareToRun (MainLoop mainLoop, Responder handler)
{
Curses.timeout (-1);
public override void PrepareToRun (MainLoop mainLoop, Responder handler)
{
Curses.timeout (-1);
mainLoop.AddWatch (0, Mono.Terminal.MainLoop.Condition.PollIn, x => {
ProcessInput (handler);
return true;
});
mainLoop.AddWatch (0, Mono.Terminal.MainLoop.Condition.PollIn, x => {
ProcessInput (handler);
return true;
});
}
}
public override void DrawFrame (Rect region, bool fill)
{
int width = region.Width;
int height = region.Height;
int b;
public override void DrawFrame (Rect region, bool fill)
{
int width = region.Width;
int height = region.Height;
int b;
Move (region.X, region.Y);
AddCh (Curses.ACS_ULCORNER);
for (b = 0; b < width - 2; b++)
AddCh (Curses.ACS_HLINE);
AddCh (Curses.ACS_URCORNER);
for (b = 1; b < height - 1; b++) {
Move (region.X, region.Y + b);
AddCh (Curses.ACS_VLINE);
if (fill) {
for (int x = 1; x < width - 1; x++)
AddCh (' ');
} else
Move (region.X + width - 1, region.Y + b);
AddCh (Curses.ACS_VLINE);
}
Move (region.X, region.Y + height - 1);
AddCh (Curses.ACS_LLCORNER);
for (b = 0; b < width - 2; b++)
AddCh (Curses.ACS_HLINE);
AddCh (Curses.ACS_LRCORNER);
}
Move (region.X, region.Y);
AddCh (Curses.ACS_ULCORNER);
for (b = 0; b < width - 2; b++)
AddCh (Curses.ACS_HLINE);
AddCh (Curses.ACS_URCORNER);
for (b = 1; b < height - 1; b++) {
Move (region.X, region.Y + b);
AddCh (Curses.ACS_VLINE);
if (fill) {
for (int x = 1; x < width - 1; x++)
AddCh (' ');
} else
Move (region.X + width - 1, region.Y + b);
AddCh (Curses.ACS_VLINE);
}
Move (region.X, region.Y + height - 1);
AddCh (Curses.ACS_LLCORNER);
for (b = 0; b < width - 2; b++)
AddCh (Curses.ACS_HLINE);
AddCh (Curses.ACS_LRCORNER);
}
public override void Init(Action terminalResized)
{
if (window != null)
return;
public override void Init (Action terminalResized)
{
if (window != null)
return;
try {
window = Curses.initscr ();
} catch (Exception e){
Console.WriteLine ("Curses failed to initialize, the exception is: " + e);
}
Curses.raw ();
Curses.noecho ();
Curses.Window.Standard.keypad (true);
this.terminalResized = terminalResized;
Colors.Base = new ColorScheme ();
Colors.Dialog = new ColorScheme ();
Colors.Menu = new ColorScheme ();
Colors.Error = new ColorScheme ();
Clip = new Rect (0, 0, Cols, Rows);
if (Curses.HasColors){
Curses.StartColor ();
Curses.UseDefaultColors ();
try {
window = Curses.initscr ();
} catch (Exception e) {
Console.WriteLine ("Curses failed to initialize, the exception is: " + e);
}
Curses.raw ();
Curses.noecho ();
Curses.Window.Standard.keypad (true);
this.terminalResized = terminalResized;
Colors.Base.Normal = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLUE);
Colors.Base.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
Colors.Base.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLUE);
Colors.Base.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN);
Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK);
Colors.Menu.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLACK);
Colors.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
Colors.Dialog.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
Colors.Dialog.HotNormal = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_WHITE);
Colors.Dialog.HotFocus = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_CYAN);
Colors.Base = new ColorScheme ();
Colors.Dialog = new ColorScheme ();
Colors.Menu = new ColorScheme ();
Colors.Error = new ColorScheme ();
Clip = new Rect (0, 0, Cols, Rows);
if (Curses.HasColors) {
Curses.StartColor ();
Curses.UseDefaultColors ();
Colors.Error.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_RED);
Colors.Error.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
Colors.Error.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_RED);
Colors.Error.HotFocus = Colors.Error.HotNormal;
} else {
Colors.Base.Normal = Curses.A_NORMAL;
Colors.Base.Focus = Curses.A_REVERSE;
Colors.Base.HotNormal = Curses.A_BOLD;
Colors.Base.HotFocus = Curses.A_BOLD | Curses.A_REVERSE;
Colors.Menu.Normal = Curses.A_REVERSE;
Colors.Menu.Focus = Curses.A_NORMAL;
Colors.Menu.HotNormal = Curses.A_BOLD;
Colors.Menu.HotFocus = Curses.A_NORMAL;
Colors.Dialog.Normal = Curses.A_REVERSE;
Colors.Dialog.Focus = Curses.A_NORMAL;
Colors.Dialog.HotNormal = Curses.A_BOLD;
Colors.Dialog.HotFocus = Curses.A_NORMAL;
Colors.Error.Normal = Curses.A_BOLD;
Colors.Error.Focus = Curses.A_BOLD | Curses.A_REVERSE;
Colors.Error.HotNormal = Curses.A_BOLD | Curses.A_REVERSE;
Colors.Error.HotFocus = Curses.A_REVERSE;
}
}
}
Colors.Base.Normal = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLUE);
Colors.Base.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
Colors.Base.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLUE);
Colors.Base.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN);
Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK);
Colors.Menu.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLACK);
Colors.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
Colors.Dialog.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
Colors.Dialog.HotNormal = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_WHITE);
Colors.Dialog.HotFocus = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_CYAN);
Colors.Error.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_RED);
Colors.Error.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
Colors.Error.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_RED);
Colors.Error.HotFocus = Colors.Error.HotNormal;
} else {
Colors.Base.Normal = Curses.A_NORMAL;
Colors.Base.Focus = Curses.A_REVERSE;
Colors.Base.HotNormal = Curses.A_BOLD;
Colors.Base.HotFocus = Curses.A_BOLD | Curses.A_REVERSE;
Colors.Menu.Normal = Curses.A_REVERSE;
Colors.Menu.Focus = Curses.A_NORMAL;
Colors.Menu.HotNormal = Curses.A_BOLD;
Colors.Menu.HotFocus = Curses.A_NORMAL;
Colors.Dialog.Normal = Curses.A_REVERSE;
Colors.Dialog.Focus = Curses.A_NORMAL;
Colors.Dialog.HotNormal = Curses.A_BOLD;
Colors.Dialog.HotFocus = Curses.A_NORMAL;
Colors.Error.Normal = Curses.A_BOLD;
Colors.Error.Focus = Curses.A_BOLD | Curses.A_REVERSE;
Colors.Error.HotNormal = Curses.A_BOLD | Curses.A_REVERSE;
Colors.Error.HotFocus = Curses.A_REVERSE;
}
}
}
}

View File

@@ -76,7 +76,7 @@ namespace Terminal {
public struct KeyEvent {
public Key Key;
public int KeyValue => (int)KeyValue;
public int KeyValue => (int)Key;
public bool IsAlt => (Key & Key.AltMask) != 0;
public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26);

View File

@@ -58,6 +58,10 @@ model, but needs revisiting in the new model.
On the demo, press tab twice, instead of selecting Ok, the first tab
does nothing, the second tab clears the screen.
=> Explanation: the Window gets a NeedsDisplay, so it displays
tiself, but the contentView does not have NeedsDisplay
set recursively, so it does not render any of the subviews
# Merge Responder into View
# Make HasFocus implicitly call SetNeedsDisplay
# Make HasFocus implicitly call SetNeedsDisplay

View File

@@ -4,177 +4,177 @@ using System.Collections.Generic;
using System.Linq;
namespace Terminal {
public enum TextAlignment {
Left, Right, Centered, Justified
}
public enum TextAlignment {
Left, Right, Centered, Justified
}
/// <summary>
/// Label widget, displays a string at a given position, can include multiple lines.
/// </summary>
public class Label : View {
List<string> lines = new List<string> ();
bool recalcPending = true;
string text;
TextAlignment textAlignment;
/// <summary>
/// Label widget, displays a string at a given position, can include multiple lines.
/// </summary>
public class Label : View {
List<string> lines = new List<string> ();
bool recalcPending = true;
string text;
TextAlignment textAlignment;
static Rect CalcRect (int x, int y, string s)
{
int mw = 0;
int ml = 1;
static Rect CalcRect (int x, int y, string s)
{
int mw = 0;
int ml = 1;
int cols = 0;
foreach (var c in s) {
if (c == '\n'){
ml++;
if (cols > mw)
mw = cols;
cols = 0;
} else
cols++;
}
return new Rect (x, y, cols, ml);
}
int cols = 0;
foreach (var c in s) {
if (c == '\n') {
ml++;
if (cols > mw)
mw = cols;
cols = 0;
} else
cols++;
}
return new Rect (x, y, cols, ml);
}
/// <summary>
/// Public constructor: creates a label at the given
/// coordinate with the given string, computes the bounding box
/// based on the size of the string, assumes that the string contains
/// newlines for multiple lines, no special breaking rules are used.
/// </summary>
public Label (int x, int y, string text) : this (CalcRect (x, y, text), text)
{
}
/// <summary>
/// Public constructor: creates a label at the given
/// coordinate with the given string, computes the bounding box
/// based on the size of the string, assumes that the string contains
/// newlines for multiple lines, no special breaking rules are used.
/// </summary>
public Label (int x, int y, string text) : this (CalcRect (x, y, text), text)
{
}
/// <summary>
/// Public constructor: creates a label at the given
/// coordinate with the given string and uses the specified
/// frame for the string.
/// </summary>
public Label (Rect rect, string text) : base (rect)
{
this.text = text;
}
/// <summary>
/// Public constructor: creates a label at the given
/// coordinate with the given string and uses the specified
/// frame for the string.
/// </summary>
public Label (Rect rect, string text) : base (rect)
{
this.text = text;
}
static char [] whitespace = new char [] { ' ', '\t' };
static char [] whitespace = new char [] { ' ', '\t' };
string ClipAndJustify (string str)
{
int slen = str.Length;
if (slen > Frame.Width)
return str.Substring (0, Frame.Width);
else {
if (textAlignment == TextAlignment.Justified) {
var words = str.Split (whitespace, StringSplitOptions.RemoveEmptyEntries);
int textCount = words.Sum ((arg) => arg.Length);
string ClipAndJustify (string str)
{
int slen = str.Length;
if (slen > Frame.Width)
return str.Substring (0, Frame.Width);
else {
if (textAlignment == TextAlignment.Justified) {
var words = str.Split (whitespace, StringSplitOptions.RemoveEmptyEntries);
int textCount = words.Sum ((arg) => arg.Length);
var spaces = (Frame.Width - textCount) / (words.Length - 1);
var extras = (Frame.Width - textCount) % words.Length;
var s = new System.Text.StringBuilder ();
//s.Append ($"tc={textCount} sp={spaces},x={extras} - ");
for (int w = 0; w < words.Length; w++) {
var x = words [w];
s.Append (x);
if (w + 1 < words.Length)
for (int i = 0; i < spaces; i++)
s.Append (' ');
if (extras > 0) {
s.Append ('_');
extras--;
}
}
return s.ToString ();
}
return str;
}
}
var spaces = (Frame.Width - textCount) / (words.Length - 1);
var extras = (Frame.Width - textCount) % words.Length;
var s = new System.Text.StringBuilder ();
//s.Append ($"tc={textCount} sp={spaces},x={extras} - ");
for (int w = 0; w < words.Length; w++) {
var x = words [w];
s.Append (x);
if (w + 1 < words.Length)
for (int i = 0; i < spaces; i++)
s.Append (' ');
if (extras > 0) {
s.Append ('_');
extras--;
}
}
return s.ToString ();
}
return str;
}
}
void Recalc ()
{
lines.Clear ();
if (text.IndexOf ('\n') == -1) {
lines.Add (ClipAndJustify (text));
return;
}
int textLen = text.Length;
int lp = 0;
for (int i = 0; i < textLen; i++) {
char c = text [i];
void Recalc ()
{
recalcPending = false;
lines.Clear ();
if (text.IndexOf ('\n') == -1) {
lines.Add (ClipAndJustify (text));
return;
}
int textLen = text.Length;
int lp = 0;
for (int i = 0; i < textLen; i++) {
char c = text [i];
if (c == '\n') {
lines.Add (ClipAndJustify (text.Substring (lp, i - lp)));
lp = i + 1;
}
}
recalcPending = false;
}
if (c == '\n') {
lines.Add (ClipAndJustify (text.Substring (lp, i - lp)));
lp = i + 1;
}
}
}
public override void Redraw (Rect region)
{
if (recalcPending)
Recalc ();
if (TextColor != -1)
Driver.SetAttribute (TextColor);
else
Driver.SetAttribute(Colors.Base.Normal);
public override void Redraw (Rect region)
{
if (recalcPending)
Recalc ();
Clear ();
Move (Frame.X, Frame.Y);
for (int line = 0; line < lines.Count; line++) {
if (line < region.Top || line >= region.Bottom)
continue;
var str = lines [line];
int x;
switch (textAlignment) {
case TextAlignment.Left:
case TextAlignment.Justified:
x = 0;
break;
case TextAlignment.Right:
x = Frame.Right - str.Length;
break;
case TextAlignment.Centered:
x = Frame.Left + (Frame.Width - str.Length) / 2;
break;
default:
throw new ArgumentOutOfRangeException ();
}
Move (x, line);
Driver.AddStr (str);
}
}
if (TextColor != -1)
Driver.SetAttribute (TextColor);
else
Driver.SetAttribute (Colors.Base.Normal);
/// <summary>
/// The text displayed by this widget.
/// </summary>
public virtual string Text {
get => text;
set {
text = value;
recalcPending = true;
SetNeedsDisplay ();
}
}
Clear ();
Move (Frame.X, Frame.Y);
for (int line = 0; line < lines.Count; line++) {
if (line < region.Top || line >= region.Bottom)
continue;
var str = lines [line];
int x;
switch (textAlignment) {
case TextAlignment.Left:
case TextAlignment.Justified:
x = 0;
break;
case TextAlignment.Right:
x = Frame.Right - str.Length;
break;
case TextAlignment.Centered:
x = Frame.Left + (Frame.Width - str.Length) / 2;
break;
default:
throw new ArgumentOutOfRangeException ();
}
Move (x, line);
Driver.AddStr (str);
}
}
public TextAlignment TextAlignment {
get => textAlignment;
set {
textAlignment = value;
SetNeedsDisplay ();
}
}
/// <summary>
/// The text displayed by this widget.
/// </summary>
public virtual string Text {
get => text;
set {
text = value;
recalcPending = true;
SetNeedsDisplay ();
}
}
/// <summary>
/// The color used for the label
/// </summary>
Attribute textColor = -1;
public Attribute TextColor {
get => textColor;
set {
textColor = value;
SetNeedsDisplay ();
}
}
}
public TextAlignment TextAlignment {
get => textAlignment;
set {
textAlignment = value;
SetNeedsDisplay ();
}
}
/// <summary>
/// The color used for the label
/// </summary>
Attribute textColor = -1;
public Attribute TextColor {
get => textColor;
set {
textColor = value;
SetNeedsDisplay ();
}
}
}
}

View File

@@ -3,299 +3,298 @@ using System.Collections.Generic;
using System.Linq;
namespace Terminal {
/// <summary>
/// Text data entry widget
/// </summary>
/// <remarks>
/// The Entry widget provides Emacs-like editing
/// functionality, and mouse support.
/// </remarks>
public class TextField : View {
string text, kill;
int first, point;
bool used;
/// <summary>
/// Text data entry widget
/// </summary>
/// <remarks>
/// The Entry widget provides Emacs-like editing
/// functionality, and mouse support.
/// </remarks>
public class TextField : View {
string text, kill;
int first, point;
bool used;
/// <summary>
/// Changed event, raised when the text has clicked.
/// </summary>
/// <remarks>
/// Client code can hook up to this event, it is
/// raised when the text in the entry changes.
/// </remarks>
public event EventHandler Changed;
/// <summary>
/// Changed event, raised when the text has clicked.
/// </summary>
/// <remarks>
/// Client code can hook up to this event, it is
/// raised when the text in the entry changes.
/// </remarks>
public event EventHandler Changed;
/// <summary>
/// Public constructor.
/// </summary>
/// <remarks>
/// </remarks>
public TextField (int x, int y, int w, string s) : base (new Rect (x, y, w, 1))
{
if (s == null)
s = "";
/// <summary>
/// Public constructor.
/// </summary>
/// <remarks>
/// </remarks>
public TextField (int x, int y, int w, string s) : base (new Rect (x, y, w, 1))
{
if (s == null)
s = "";
text = s;
point = s.Length;
first = point > w ? point - w : 0;
CanFocus = true;
Color = Colors.Dialog.Focus;
}
text = s;
point = s.Length;
first = point > w ? point - w : 0;
CanFocus = true;
Color = Colors.Dialog.Focus;
}
/// <summary>
/// Sets or gets the text in the entry.
/// </summary>
/// <remarks>
/// </remarks>
public string Text {
get {
return text;
}
/// <summary>
/// Sets or gets the text in the entry.
/// </summary>
/// <remarks>
/// </remarks>
public string Text {
get {
return text;
}
set {
text = value;
if (point > text.Length)
point = text.Length;
first = point > Frame.Width ? point - Frame.Width : 0;
SetNeedsDisplay ();
}
}
set {
text = value;
if (point > text.Length)
point = text.Length;
first = point > Frame.Width ? point - Frame.Width : 0;
SetNeedsDisplay ();
}
}
/// <summary>
/// Sets the secret property.
/// </summary>
/// <remarks>
/// This makes the text entry suitable for entering passwords.
/// </remarks>
public bool Secret { get; set; }
/// <summary>
/// Sets the secret property.
/// </summary>
/// <remarks>
/// This makes the text entry suitable for entering passwords.
/// </remarks>
public bool Secret { get; set; }
Attribute color;
/// <summary>
/// Sets the color attribute to use (includes foreground and background).
/// </summary>
/// <value>The color.</value>
public Attribute Color {
get => color;
set {
color = value;
SetNeedsDisplay ();
}
}
Attribute color;
/// <summary>
/// Sets the color attribute to use (includes foreground and background).
/// </summary>
/// <value>The color.</value>
public Attribute Color {
get => color;
set {
color = value;
SetNeedsDisplay ();
}
}
/// <summary>
/// The current cursor position.
/// </summary>
public int CursorPosition { get { return point; } }
/// <summary>
/// The current cursor position.
/// </summary>
public int CursorPosition { get { return point; } }
/// <summary>
/// Sets the cursor position.
/// </summary>
public override void PositionCursor ()
{
Move (point - first, 0);
}
/// <summary>
/// Sets the cursor position.
/// </summary>
public override void PositionCursor ()
{
Move (point - first, 0);
}
public override void Redraw (Rect region)
{
Driver.SetAttribute (Color);
Move (0, 0);
public override void Redraw (Rect region)
{
Driver.SetAttribute (Color);
Move (0, 0);
for (int i = 0; i < Frame.Width; i++) {
int p = first + i;
for (int i = 0; i < Frame.Width; i++) {
int p = first + i;
if (p < text.Length) {
Driver.AddCh (Secret ? '*' : text [p]);
} else
Driver.AddCh (' ');
}
PositionCursor ();
}
if (p < text.Length) {
Driver.AddCh (Secret ? '*' : text [p]);
} else
Driver.AddCh ('_');
}
PositionCursor ();
}
void Adjust ()
{
if (point < first)
first = point;
else if (first + point >= Frame.Width)
first = point - (Frame.Width / 3);
Redraw (Bounds);
Driver.Refresh ();
}
void Adjust ()
{
if (point < first)
first = point;
else if (first + point >= Frame.Width)
first = point - (Frame.Width / 3);
SetNeedsDisplay ();
}
void SetText (string new_text)
{
text = new_text;
if (Changed != null)
Changed (this, EventArgs.Empty);
}
void SetText (string new_text)
{
text = new_text;
if (Changed != null)
Changed (this, EventArgs.Empty);
}
public override bool CanFocus {
get => true;
set { base.CanFocus = value; }
}
public override bool CanFocus {
get => true;
set { base.CanFocus = value; }
}
public override bool ProcessKey (KeyEvent kb)
{
switch (kb.Key) {
case Key.Delete:
case Key.Backspace:
if (point == 0)
return true;
public override bool ProcessKey (KeyEvent kb)
{
switch (kb.Key) {
case Key.Delete:
case Key.Backspace:
if (point == 0)
return true;
SetText (text.Substring (0, point - 1) + text.Substring (point));
point--;
Adjust ();
break;
SetText (text.Substring (0, point - 1) + text.Substring (point));
point--;
Adjust ();
break;
// Home, C-A
case Key.Home:
case Key.ControlA:
point = 0;
Adjust ();
break;
// Home, C-A
case Key.Home:
case Key.ControlA:
point = 0;
Adjust ();
break;
case Key.CursorLeft:
case Key.ControlB:
if (point > 0) {
point--;
Adjust ();
}
break;
case Key.CursorLeft:
case Key.ControlB:
if (point > 0) {
point--;
Adjust ();
}
break;
case Key.ControlD: // Delete
if (point == text.Length)
break;
SetText (text.Substring (0, point) + text.Substring (point + 1));
Adjust ();
break;
case Key.ControlD: // Delete
if (point == text.Length)
break;
SetText (text.Substring (0, point) + text.Substring (point + 1));
Adjust ();
break;
case Key.ControlE: // End
point = text.Length;
Adjust ();
break;
case Key.ControlE: // End
point = text.Length;
Adjust ();
break;
case Key.CursorRight:
case Key.ControlF:
if (point == text.Length)
break;
point++;
Adjust ();
break;
case Key.CursorRight:
case Key.ControlF:
if (point == text.Length)
break;
point++;
Adjust ();
break;
case Key.ControlK: // kill-to-end
kill = text.Substring (point);
SetText (text.Substring (0, point));
Adjust ();
break;
case Key.ControlK: // kill-to-end
kill = text.Substring (point);
SetText (text.Substring (0, point));
Adjust ();
break;
case Key.ControlY: // Control-y, yank
if (kill == null)
return true;
case Key.ControlY: // Control-y, yank
if (kill == null)
return true;
if (point == text.Length) {
SetText (text + kill);
point = text.Length;
} else {
SetText (text.Substring (0, point) + kill + text.Substring (point));
point += kill.Length;
}
Adjust ();
break;
if (point == text.Length) {
SetText (text + kill);
point = text.Length;
} else {
SetText (text.Substring (0, point) + kill + text.Substring (point));
point += kill.Length;
}
Adjust ();
break;
case (Key)((int)'b' + Key.AltMask):
int bw = WordBackward (point);
if (bw != -1)
point = bw;
Adjust ();
break;
case (Key)((int)'b' + Key.AltMask):
int bw = WordBackward (point);
if (bw != -1)
point = bw;
Adjust ();
break;
case (Key)((int)'f' + Key.AltMask):
int fw = WordForward (point);
if (fw != -1)
point = fw;
Adjust ();
break;
case (Key)((int)'f' + Key.AltMask):
int fw = WordForward (point);
if (fw != -1)
point = fw;
Adjust ();
break;
default:
// Ignore other control characters.
if (kb.Key < Key.Space || kb.Key > Key.CharMask)
return false;
default:
// Ignore other control characters.
if (kb.Key < Key.Space || kb.Key > Key.CharMask)
return false;
if (used) {
if (point == text.Length) {
SetText (text + (char)kb.Key);
} else {
SetText (text.Substring (0, point) + (char)kb.Key + text.Substring (point));
}
point++;
} else {
SetText ("" + (char)kb.Key);
first = 0;
point = 1;
}
used = true;
Adjust ();
return true;
}
used = true;
return true;
}
if (used) {
if (point == text.Length) {
SetText (text + (char)kb.Key);
} else {
SetText (text.Substring (0, point) + (char)kb.Key + text.Substring (point));
}
point++;
} else {
SetText ("" + (char)kb.Key);
first = 0;
point = 1;
}
used = true;
Adjust ();
return true;
}
used = true;
return true;
}
int WordForward (int p)
{
if (p >= text.Length)
return -1;
int WordForward (int p)
{
if (p >= text.Length)
return -1;
int i = p;
if (Char.IsPunctuation (text [p]) || Char.IsWhiteSpace (text [p])) {
for (; i < text.Length; i++) {
if (Char.IsLetterOrDigit (text [i]))
break;
}
for (; i < text.Length; i++) {
if (!Char.IsLetterOrDigit (text [i]))
break;
}
} else {
for (; i < text.Length; i++) {
if (!Char.IsLetterOrDigit (text [i]))
break;
}
}
if (i != p)
return i;
return -1;
}
int i = p;
if (Char.IsPunctuation (text [p]) || Char.IsWhiteSpace (text [p])) {
for (; i < text.Length; i++) {
if (Char.IsLetterOrDigit (text [i]))
break;
}
for (; i < text.Length; i++) {
if (!Char.IsLetterOrDigit (text [i]))
break;
}
} else {
for (; i < text.Length; i++) {
if (!Char.IsLetterOrDigit (text [i]))
break;
}
}
if (i != p)
return i;
return -1;
}
int WordBackward (int p)
{
if (p == 0)
return -1;
int WordBackward (int p)
{
if (p == 0)
return -1;
int i = p - 1;
if (i == 0)
return 0;
int i = p - 1;
if (i == 0)
return 0;
if (Char.IsPunctuation (text [i]) || Char.IsSymbol (text [i]) || Char.IsWhiteSpace (text [i])) {
for (; i >= 0; i--) {
if (Char.IsLetterOrDigit (text [i]))
break;
}
for (; i >= 0; i--) {
if (!Char.IsLetterOrDigit (text [i]))
break;
}
} else {
for (; i >= 0; i--) {
if (!Char.IsLetterOrDigit (text [i]))
break;
}
}
i++;
if (Char.IsPunctuation (text [i]) || Char.IsSymbol (text [i]) || Char.IsWhiteSpace (text [i])) {
for (; i >= 0; i--) {
if (Char.IsLetterOrDigit (text [i]))
break;
}
for (; i >= 0; i--) {
if (!Char.IsLetterOrDigit (text [i]))
break;
}
} else {
for (; i >= 0; i--) {
if (!Char.IsLetterOrDigit (text [i]))
break;
}
}
i++;
if (i != p)
return i;
if (i != p)
return i;
return -1;
}
return -1;
}
#if false
public override void ProcessMouse (Curses.MouseEvent ev)
@@ -315,7 +314,7 @@ namespace Terminal {
SetNeedsDisplay ();
}
#endif
}
}
}

32
demo.cs
View File

@@ -1,20 +1,20 @@
using Terminal;
class Demo {
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 Label (3, 14, "Login: "),
new TextField (10, 14, 40, ""),
new Button (3, 16, "Ok")
};
top.Add (win);
Application.Run ();
}
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, ""),
};
top.Add (win);
Application.Run ();
}
}