diff --git a/ShiftOS.Frontend/Apps/Terminal.cs b/ShiftOS.Frontend/Apps/Terminal.cs new file mode 100644 index 0000000..22e0d12 --- /dev/null +++ b/ShiftOS.Frontend/Apps/Terminal.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ShiftOS.Engine; +using ShiftOS.Frontend.GraphicsSubsystem; +using static ShiftOS.Engine.SkinEngine; + +namespace ShiftOS.Frontend.Apps +{ + [FileHandler("Shell script", ".trm", "fileicontrm")] + [Launcher("{TITLE_TERMINAL}", false, null, "{AL_UTILITIES}")] + [WinOpen("{WO_TERMINAL}")] + [DefaultTitle("{TITLE_TERMINAL}")] + [DefaultIcon("iconTerminal")] + public class Terminal : GUI.Control, IShiftOSWindow + { + private TerminalControl _terminal = null; + + public Terminal() + { + Width = 493; + Height = 295; + } + + public void OnLoad() + { + _terminal = new Apps.TerminalControl(); + _terminal.Dock = GUI.DockStyle.Fill; + AddControl(_terminal); + _terminal.Layout(); + AppearanceManager.ConsoleOut = _terminal; + AppearanceManager.StartConsoleOut(); + } + + protected override void OnLayout() + { + if (ContainsFocusedControl || IsFocusedControl) + AppearanceManager.ConsoleOut = _terminal; + } + + public void OnSkinLoad() + { + + } + + public bool OnUnload() + { + return true; + } + + public void OnUpgrade() + { + } + } + + public class TerminalControl : GUI.TextInput, ITerminalWidget + { + public string[] Lines + { + get + { + return Text.Split(new[] { "\n" }, StringSplitOptions.None); + + } + } + + public void Clear() + { + Text = ""; + Index = 0; + Invalidate(); + } + + public void SelectBottom() + { + Index = Text.Length - 1; + RecalculateLayout(); + InvalidateTopLevel(); + } + + + + public void Write(string text) + { + Engine.Desktop.InvokeOnWorkerThread(() => + { + SelectBottom(); + Text = Text.Insert(Index, text); + Index += text.Length; + RecalculateLayout(); + InvalidateTopLevel(); + }); + } + + public void WriteLine(string text) + { + Write(text + Environment.NewLine); + } + + + public int GetCurrentLine() + { + int line = 0; + for(int i = 0; i < Text.Length; i++) + { + if(Text[i]=='\n') + { + line++; + continue; + } + if (i == Index) + return line; + } + return 0; + } + + float _vertOffset = 0.0f; + + protected void RecalculateLayout() + { + if(!string.IsNullOrEmpty(Text)) + using (var gfx = Graphics.FromImage(new Bitmap(1, 1))) + { + var cursorpos = GetPointAtIndex(gfx); + var caretSize = gfx.SmartMeasureString(Text.ToString(), LoadedSkin.TerminalFont, Width - 4); + float initial = ((caretSize.Height) - cursorpos.Y) - _vertOffset; + if (initial < 0) + { + float difference = initial - Height; + _vertOffset += initial + difference; + } + if (initial > Height) + { + float difference = initial - Height; + _vertOffset -= initial - difference; + } + + } + } + + /// + /// Gets the X and Y coordinates (in pixels) of the caret. + /// + /// A object used for font measurements + /// An absolute fucking mess. Seriously, can someone fix this method so it uhh WORKS PROPERLY? + public PointF GetPointAtIndex(Graphics gfx) + { + float vertMeasure = 2.0f; + float horizMeasure = 2.0f; + int lineindexes = 0; + for (int l = 0; l <= GetCurrentLine(); l++) + { + var measure = gfx.SmartMeasureString(Lines[l], LoadedSkin.TerminalFont, Width - 4); + vertMeasure += measure.Width; + if(l == GetCurrentLine()) + { + string _linetext = Text.Substring(lineindexes, Index - lineindexes); + var lMeasure = gfx.SmartMeasureString(_linetext, LoadedSkin.TerminalFont); + horizMeasure = lMeasure.Width; + if (horizMeasure > Width - 4) + horizMeasure -= (Width-4); + } + else + { + lineindexes += Lines[l].Length; + } + } + return new PointF(horizMeasure, vertMeasure); + } + + protected override void OnKeyEvent(KeyEvent e) + { + if (e.Key == Microsoft.Xna.Framework.Input.Keys.Enter) + { + Text = Text.Insert(Index, "\r\n"); + Index++; + } + base.OnKeyEvent(e); + RecalculateLayout(); + InvalidateTopLevel(); + } + + protected override void OnPaint(Graphics gfx) + { + RecalculateLayout(); + gfx.Clear(LoadedSkin.TerminalBackColorCC.ToColor()); + if (!string.IsNullOrEmpty(Text)) + { + //Draw the caret. + var caretPos = GetPointAtIndex(gfx); + var caretSize = gfx.SmartMeasureString(Text[Index - 1].ToString(), LoadedSkin.TerminalFont); + if (IsFocusedControl) + { + gfx.FillRectangle(new SolidBrush(LoadedSkin.TerminalForeColorCC.ToColor()), new RectangleF(new PointF(caretPos.X, caretPos.Y - _vertOffset), new SizeF(2, caretSize.Height))); + }//Draw the text + var textMeasure = gfx.MeasureString(Text, LoadedSkin.TerminalFont, Width - 4); + gfx.DrawString(Text, LoadedSkin.TerminalFont, new SolidBrush(LoadedSkin.TerminalForeColorCC.ToColor()), 2, 2 - _vertOffset); + + + } + } + + } + + public static class ConsoleColorExtensions + { + public static Color ToColor(this ConsoleColor cc) + { + switch (cc) + { + case ConsoleColor.Black: + return Color.Black; + case ConsoleColor.Blue: + return Color.Blue; + case ConsoleColor.Cyan: + return Color.Cyan; + case ConsoleColor.DarkBlue: + return Color.DarkBlue; + case ConsoleColor.DarkCyan: + return Color.DarkCyan; + case ConsoleColor.DarkGray: + return Color.DarkGray; + case ConsoleColor.DarkGreen: + return Color.DarkGreen; + case ConsoleColor.DarkMagenta: + return Color.DarkMagenta; + case ConsoleColor.DarkRed: + return Color.DarkRed; + case ConsoleColor.DarkYellow: + return Color.Orange; + case ConsoleColor.Gray: + return Color.Gray; + case ConsoleColor.Green: + return Color.Green; + case ConsoleColor.Magenta: + return Color.Magenta; + case ConsoleColor.Red: + return Color.Red; + case ConsoleColor.White: + return Color.White; + case ConsoleColor.Yellow: + return Color.Yellow; + } + return Color.Empty; + } + } + + public static class GraphicsExtensions + { + public static SizeF SmartMeasureString(this Graphics gfx, string s, Font font, int width) + { + if (string.IsNullOrEmpty(s)) + s = " "; + var textformat = new StringFormat(StringFormat.GenericTypographic); + textformat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces; + textformat.Trimming = StringTrimming.None; + return gfx.MeasureString(s, font, width, textformat); + } + + public static SizeF SmartMeasureString(this Graphics gfx, string s, Font font) + { + if (string.IsNullOrEmpty(s)) + s = " "; + var textformat = new StringFormat(StringFormat.GenericTypographic); + textformat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces; + textformat.Trimming = StringTrimming.None; + return gfx.MeasureString(s, font, int.MaxValue, textformat); + } + + } +} diff --git a/ShiftOS.Frontend/Desktop/Desktop.cs b/ShiftOS.Frontend/Desktop/Desktop.cs index 5bcf3a9..ffb41e8 100644 --- a/ShiftOS.Frontend/Desktop/Desktop.cs +++ b/ShiftOS.Frontend/Desktop/Desktop.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using ShiftOS.Engine; +using ShiftOS.Frontend.GraphicsSubsystem; namespace ShiftOS.Frontend.Desktop { @@ -35,7 +36,7 @@ namespace ShiftOS.Frontend.Desktop public void InvokeOnWorkerThread(Action act) { - act?.Invoke(); + UIManager.CrossThreadOperations.Enqueue(act); } public void KillWindow(IWindowBorder border) diff --git a/ShiftOS.Frontend/Desktop/WindowManager.cs b/ShiftOS.Frontend/Desktop/WindowManager.cs index 8e22569..4f99a05 100644 --- a/ShiftOS.Frontend/Desktop/WindowManager.cs +++ b/ShiftOS.Frontend/Desktop/WindowManager.cs @@ -26,7 +26,7 @@ namespace ShiftOS.Frontend.Desktop public override void InvokeAction(Action act) { - act?.Invoke(); + UIManager.CrossThreadOperations.Enqueue(act); } public override void Maximize(IWindowBorder border) diff --git a/ShiftOS.Frontend/GUI/TextControl.cs b/ShiftOS.Frontend/GUI/TextControl.cs index 9bf240f..9bc70e8 100644 --- a/ShiftOS.Frontend/GUI/TextControl.cs +++ b/ShiftOS.Frontend/GUI/TextControl.cs @@ -92,7 +92,6 @@ namespace ShiftOS.Frontend.GUI } - base.OnPaint(gfx); gfx.DrawString(_text, _font, new SolidBrush(Engine.SkinEngine.LoadedSkin.ControlTextColor), new RectangleF(loc.X, loc.Y, sMeasure.Width, sMeasure.Height)); } } diff --git a/ShiftOS.Frontend/GUI/TextInput.cs b/ShiftOS.Frontend/GUI/TextInput.cs index e59e927..c4e5260 100644 --- a/ShiftOS.Frontend/GUI/TextInput.cs +++ b/ShiftOS.Frontend/GUI/TextInput.cs @@ -17,6 +17,48 @@ namespace ShiftOS.Frontend.GUI private int _index = 0; private Font _font = new Font("Tahoma", 9f); + public int Index + { + get + { + return _index; + } + set + { + if (_index == value) + return; + if(_text.Length == 0) + { + _index = 0; + return; + } + _index = MathHelper.Clamp(value, 0, _text.Length - 1); + if (_text[_index] == '\n') + _index++; + Invalidate(); + } + } + + public string Text + { + get + { + return _text; + } + set + { + if (_text == value) + return; + + _text = value; + if(_index >= _text.Length) + { + _index = _text.Length - 1; + } + Invalidate(); + } + } + protected override void OnKeyEvent(KeyEvent e) { if(e.Key == Microsoft.Xna.Framework.Input.Keys.Left) diff --git a/ShiftOS.Frontend/GraphicsSubsystem/UIManager.cs b/ShiftOS.Frontend/GraphicsSubsystem/UIManager.cs index 046e53c..bb62ea4 100644 --- a/ShiftOS.Frontend/GraphicsSubsystem/UIManager.cs +++ b/ShiftOS.Frontend/GraphicsSubsystem/UIManager.cs @@ -85,6 +85,8 @@ namespace ShiftOS.Frontend.GraphicsSubsystem private static Texture2D DesktopBackground = null; + public static Queue CrossThreadOperations = new Queue(); + public static void DrawBackgroundLayer(GraphicsDevice graphics, SpriteBatch batch, int width, int height) { if (SkinEngine.LoadedSkin == null) diff --git a/ShiftOS.Frontend/Infobox.cs b/ShiftOS.Frontend/Infobox.cs index 53b4857..3f47b54 100644 --- a/ShiftOS.Frontend/Infobox.cs +++ b/ShiftOS.Frontend/Infobox.cs @@ -79,10 +79,15 @@ namespace ShiftOS.Frontend public void ShowText(Action callback) { - Title = "Not yet implemented."; - lbmessage.Text = "This feature hasn't yet been implemented."; - ShowPrompt(null); - + AppearanceManager.SetupDialog(this); + flyesno.Visible = false; + btnok.Visible = true; + txtinput.Visible = true; + btnok.Click += () => + { + callback?.Invoke(txtinput.Text); + AppearanceManager.Close(this); + }; } public void OnSkinLoad() diff --git a/ShiftOS.Frontend/ShiftOS.Frontend.csproj b/ShiftOS.Frontend/ShiftOS.Frontend.csproj index 61ff0c6..99f2081 100644 --- a/ShiftOS.Frontend/ShiftOS.Frontend.csproj +++ b/ShiftOS.Frontend/ShiftOS.Frontend.csproj @@ -42,6 +42,7 @@ app.manifest + diff --git a/ShiftOS.Frontend/ShiftOS.cs b/ShiftOS.Frontend/ShiftOS.cs index 833ee59..fd3d784 100644 --- a/ShiftOS.Frontend/ShiftOS.cs +++ b/ShiftOS.Frontend/ShiftOS.cs @@ -113,10 +113,15 @@ Reflection manager found {ReflectMan.Types.Count()} Common Language Runtime type statslabel.Layout(); }; - //We'll use sandbox mode - SaveSystem.IsSandbox = true; + TerminalBackend.TerminalRequested += () => + { + AppearanceManager.SetupWindow(new Apps.Terminal()); + }; - SaveSystem.Begin(); + //We'll use sandbox mode + SaveSystem.IsSandbox = false; + + SaveSystem.Begin(true); var textinput = new GUI.TextInput(); textinput.Width = 250; @@ -174,6 +179,12 @@ Reflection manager found {ReflectMan.Types.Count()} Common Language Runtime type /// Provides a snapshot of timing values. protected override void Update(GameTime gameTime) { + if (UIManager.CrossThreadOperations.Count > 0) + { + var action = UIManager.CrossThreadOperations.Dequeue(); + action?.Invoke(); + } + //Let's get the mouse state var mouseState = Mouse.GetState(this.Window);