From: Jean-Philippe Bruyère Date: Sun, 2 Apr 2017 11:58:42 +0000 (+0200) Subject: basic scrolling text editor X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=49d1b102c587d9e59026448e8536a9f71b2e4917;p=jp%2Fcrowedit.git basic scrolling text editor --- diff --git a/CrowEdit.cs b/CrowEdit.cs deleted file mode 100644 index 1e0323f..0000000 --- a/CrowEdit.cs +++ /dev/null @@ -1,228 +0,0 @@ -// -// Main.cs -// -// Author: -// Jean-Philippe Bruyère -// -// Copyright (c) 2017 jp -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -using System; -using Crow; -using System.IO; -using System.Collections.Generic; - -namespace CrowEdit -{ - public class CrowEdit : CrowWindow - { - public Command CMDNew, CMDOpen, CMDSave, CMDSaveAs, CMDQuit, CMDUndo, CMDRedo, CMDCut, CMDCopy, CMDPaste, CMDHelp, CMDAbout; - - string _curDir = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments); - string _curFilePath = "unamed.txt"; - string _text = "", _origText=""; - - List undoStack = new List(); - List redoStack = new List(); - - public string Text { - get { return _text; } - set { - if (_text == value) - return; - undoStack.Add (_text); - CMDUndo.CanExecute = true; - redoStack.Clear (); - CMDRedo.CanExecute = false; - _text = value; - NotifyValueChanged ("Text", _text); - NotifyValueChanged ("IsDirty", IsDirty); - } - } - bool isDirty = false; - public bool IsDirty { get { return _text != _origText; }} - - public string CurrentDir { - get { return _curDir; } - set { - if (_curDir == value) - return; - _curDir = value; - NotifyValueChanged ("CurrentDir", _curDir); - } - } - public string CurFilePath { - get { return _curFilePath; } - set { - if (_curFilePath == value) - return; - _curFilePath = value; - NotifyValueChanged ("CurFilePath", _curFilePath); - } - } - public string CurFileFullPath { get { return Path.Combine(CurrentDir,CurFilePath); }} - - void initCommands(){ - CMDNew = new Command(new Action(() => newFile())) { Caption = "New", Icon = new SvgPicture("#CrowEdit.ui.icons.blank-file.svg")}; - CMDOpen = new Command(new Action(() => openFileDialog())) { Caption = "Open...", Icon = new SvgPicture("#CrowEdit.ui.icons.outbox.svg")}; - CMDSave = new Command(new Action(() => saveFileDialog())) { Caption = "Save", Icon = new SvgPicture("#CrowEdit.ui.icons.inbox.svg"), CanExecute = false}; - CMDSaveAs = new Command(new Action(() => saveFileDialog())) { Caption = "Save As...", Icon = new SvgPicture("#CrowEdit.ui.icons.inbox.svg"), CanExecute = false}; - CMDQuit = new Command(new Action(() => Quit (null, null))) { Caption = "Quit", Icon = new SvgPicture("#CrowEdit.ui.icons.sign-out.svg")}; - CMDUndo = new Command(new Action(() => undo())) { Caption = "Undo", Icon = new SvgPicture("#CrowEdit.ui.icons.reply.svg"), CanExecute = false}; - CMDRedo = new Command(new Action(() => redo())) { Caption = "Redo", Icon = new SvgPicture("#CrowEdit.ui.icons.share-arrow.svg"), CanExecute = false}; - CMDCut = new Command(new Action(() => Quit (null, null))) { Caption = "Cut", Icon = new SvgPicture("#CrowEdit.ui.icons.scissors.svg"), CanExecute = false}; - CMDCopy = new Command(new Action(() => Quit (null, null))) { Caption = "Copy", Icon = new SvgPicture("#CrowEdit.ui.icons.copy-file.svg"), CanExecute = false}; - CMDPaste = new Command(new Action(() => Quit (null, null))) { Caption = "Paste", Icon = new SvgPicture("#CrowEdit.ui.icons.paste-on-document.svg"), CanExecute = false}; - CMDHelp = new Command(new Action(() => System.Diagnostics.Debug.WriteLine("help"))) { Caption = "Help", Icon = new SvgPicture("#CrowEdit.ui.icons.question.svg")}; - - } - void newFile(){ - CurFilePath = "unamed.txt"; - _origText = _text = ""; - NotifyValueChanged ("Text", _text); - NotifyValueChanged ("IsDirty", false); - redoStack.Clear (); - undoStack.Clear (); - CMDRedo.CanExecute = false; - CMDUndo.CanExecute = false; - NotifyValueChanged ("CurFileFullPath", CurFileFullPath); - } - void undo(){ - string step = undoStack [undoStack.Count -1]; - redoStack.Add (_text); - CMDRedo.CanExecute = true; - undoStack.RemoveAt(undoStack.Count -1); - - _text = step; - - NotifyValueChanged ("Text", _text); - NotifyValueChanged ("IsDirty", IsDirty); - - if (undoStack.Count == 0) - CMDUndo.CanExecute = false; - } - void redo(){ - string step = redoStack [redoStack.Count -1]; - undoStack.Add (_text); - CMDUndo.CanExecute = true; - redoStack.RemoveAt(redoStack.Count -1); - _text = step; - NotifyValueChanged ("Text", _text); - NotifyValueChanged ("IsDirty", IsDirty); - - if (redoStack.Count == 0) - CMDRedo.CanExecute = false; - } - void openFileDialog(){ - Load ("#CrowEdit.ui.openFile.crow").DataSource = this; - } - void saveFileDialog(){ - Load ("#CrowEdit.ui.saveFile.crow").DataSource = this; - } - void openFileDialog_OkClicked (object sender, EventArgs e) - { - FileDialog fd = sender as FileDialog; - if (string.IsNullOrEmpty (fd.SelectedFile)) - return; - CurFilePath = fd.SelectedFile; - CurrentDir = fd.SelectedDirectory; - - using (StreamReader sr = new StreamReader (CurFileFullPath)) { - _origText = sr.ReadToEnd (); - } - _text = _origText; - - NotifyValueChanged ("Text", _text); - NotifyValueChanged ("IsDirty", false); - redoStack.Clear (); - undoStack.Clear (); - CMDRedo.CanExecute = false; - CMDUndo.CanExecute = false; - - NotifyValueChanged ("CurFileFullPath", CurFileFullPath); - } - void saveFileDialog_OkClicked (object sender, EventArgs e) - { - FileDialog fd = sender as FileDialog; - - if (!string.IsNullOrEmpty (fd.SelectedFile)) - CurFilePath = fd.SelectedFile; - CurrentDir = fd.SelectedDirectory; - - System.Diagnostics.Debug.WriteLine (CurFileFullPath); -// using (StreamWriter sr = new StreamWriter (fd.SelectedFile)) { -// sr.Write(_text); -// } - _origText = _text; - - NotifyValueChanged ("IsDirty", false); - NotifyValueChanged ("CurFileFullPath", CurFileFullPath); - } - void onTextChanged (object sender, TextChangeEventArgs e) - { - System.Diagnostics.Debug.WriteLine ("text changed"); - NotifyValueChanged ("IsDirty", IsDirty); - } - - [STAThread] - static void Main () - { - using (CrowEdit win = new CrowEdit ()) { - win.Run (30); - } - } - public CrowEdit () - : base(800, 600,"Crow Simple Editor") - {} - - protected override void OnLoad (EventArgs e) - { - base.OnLoad (e); - - this.ValueChanged += CrowEdit_ValueChanged; - initCommands (); - - Load ("#CrowEdit.ui.main.crow").DataSource = this; - NotifyValueChanged ("CurFileFullPath", CurFileFullPath); - } - - void textView_KeyDown (object sender, Crow.KeyboardKeyEventArgs e) - { - if (e.Control) { - if (e.Key == Key.W) { - if (e.Shift) - CMDRedo.Execute (); - else - CMDUndo.Execute (); - } - } - } - - void CrowEdit_ValueChanged (object sender, ValueChangeEventArgs e) - { - if (e.MemberName == "IsDirty" && isDirty != (bool)e.NewValue) { - isDirty = (bool)e.NewValue; - if (isDirty) { - CMDSave.CanExecute = true; - CMDSaveAs.CanExecute = true; - }else{ - CMDSave.CanExecute = false; - CMDSaveAs.CanExecute = false; - } - } - } - - } -} - diff --git a/CrowEdit.csproj b/CrowEdit.csproj index 4f9c898..b34a2c4 100644 --- a/CrowEdit.csproj +++ b/CrowEdit.csproj @@ -61,25 +61,30 @@ - - packages\Crow.OpenTK.0.5.1\lib\net45\Crow.dll - gtk-sharp-3.0 + + packages\Crow.OpenTK.0.5.1\lib\net45\Crow.dll + - - - + + + + + + + + diff --git a/OpenGL/Extensions.cs b/OpenGL/Extensions.cs new file mode 100644 index 0000000..0dd1cca --- /dev/null +++ b/OpenGL/Extensions.cs @@ -0,0 +1,41 @@ +// +// Extensions.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2017 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +using OpenTK; +using Crow; + +namespace Crow +{ + public static class Extensions { + public static Vector4 ToVector4(this Color c){ + float[] f = c.floatArray; + return new Vector4 (f [0], f [1], f [2], f [3]); + } + public static Vector3 Transform(this Vector3 v, Matrix4 m){ + return Vector4.Transform(new Vector4(v, 1), m).Xyz; + } + public static bool IsInBetween(this int v, int min, int max){ + return v >= min & v <= max; + } + + } +} + diff --git a/OpenGL/Shader.cs b/OpenGL/Shader.cs new file mode 100644 index 0000000..6ae99b8 --- /dev/null +++ b/OpenGL/Shader.cs @@ -0,0 +1,336 @@ +// +// Shader.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2016 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Diagnostics; +using System.IO; +using OpenTK; +using OpenTK.Graphics.OpenGL; + +namespace Crow +{ + public class Shader : IDisposable + { + #region CTOR + public Shader () + { + Init (); + } + public Shader (string vertResPath, string fragResPath = null, string geomResPath = null) + { + VertSourcePath = vertResPath; + FragSourcePath = fragResPath; + GeomSourcePath = geomResPath; + + loadSourcesFiles (); + + Init (); + } + #endregion + + public string VertSourcePath, + FragSourcePath, + GeomSourcePath; + #region Sources + protected string _vertSource = @" + #version 330 + precision lowp float; + + uniform mat4 mvp; + + layout(location = 0) in vec3 in_position; + layout(location = 1) in vec2 in_tex; + + out vec2 texCoord; + + void main(void) + { + texCoord = in_tex; + gl_Position = mvp * vec4(in_position, 1.0); + }"; + + protected string _fragSource = @" + #version 330 + precision lowp float; + + uniform sampler2D tex; + + in vec2 texCoord; + out vec4 out_frag_color; + + void main(void) + { + out_frag_color = texture( tex, texCoord);//vec4(1,0,0,1); + }"; + string _geomSource = @""; +// #version 330 +// layout(triangles) in; +// layout(triangle_strip, max_vertices=3) out; +// void main() +// { +// for(int i=0; i<3; i++) +// { +// gl_Position = gl_in[i].gl_Position; +// EmitVertex(); +// } +// EndPrimitive(); +// }"; + #endregion + + #region Private and protected fields + public int vsId, fsId, gsId, pgmId, mvpLocation; + + Matrix4 mvp = Matrix4.Identity; + #endregion + + + #region Public properties + public virtual string vertSource + { + get { return _vertSource;} + set { _vertSource = value; } + } + public virtual string fragSource + { + get { return _fragSource;} + set { _fragSource = value; } + } + public virtual string geomSource + { + get { return _geomSource; } + set { _geomSource = value; } + } + + public virtual Matrix4 MVP{ + set { mvp = value; } + get { return mvp; } + } + #endregion + + #region Public functions + /// + /// configure sources and compile + /// + public virtual void Init() + { + Compile (); + } + public void Reload(){ + loadSourcesFiles (); + Compile (); + } + public void SetSource(ShaderType shaderType, string _source){ + switch (shaderType) { + case ShaderType.FragmentShader: + fragSource = _source; + return; + case ShaderType.VertexShader: + vertSource = _source; + return; + case ShaderType.GeometryShader: + geomSource = _source; + return; + } + } + public string GetSource(ShaderType shaderType){ + switch (shaderType) { + case ShaderType.FragmentShader: + return fragSource; + case ShaderType.VertexShader: + return vertSource; + case ShaderType.GeometryShader: + return geomSource; + } + return ""; + } + public string GetSourcePath(ShaderType shaderType){ + switch (shaderType) { + case ShaderType.FragmentShader: + return FragSourcePath; + case ShaderType.VertexShader: + return VertSourcePath; + case ShaderType.GeometryShader: + return GeomSourcePath; + } + return ""; + } + public virtual void Compile() + { + Dispose (); + + pgmId = GL.CreateProgram(); + + if (!string.IsNullOrEmpty(vertSource)) + { + vsId = GL.CreateShader(ShaderType.VertexShader); + compileShader(vsId, vertSource); + } + if (!string.IsNullOrEmpty(fragSource)) + { + fsId = GL.CreateShader(ShaderType.FragmentShader); + compileShader(fsId, fragSource); + + } + if (!string.IsNullOrEmpty(geomSource)) + { + gsId = GL.CreateShader(ShaderType.GeometryShader); + compileShader(gsId,geomSource); + } + + if (vsId != 0) + GL.AttachShader(pgmId, vsId); + if (fsId != 0) + GL.AttachShader(pgmId, fsId); + if (gsId != 0) + GL.AttachShader(pgmId, gsId); + + BindVertexAttributes (); + + string info; + GL.LinkProgram(pgmId); + GL.GetProgramInfoLog(pgmId, out info); + + if (!string.IsNullOrEmpty (info)) { + Debug.WriteLine ("Linkage:"); + Debug.WriteLine (info); + } + + info = null; + + GL.ValidateProgram(pgmId); + GL.GetProgramInfoLog(pgmId, out info); + if (!string.IsNullOrEmpty (info)) { + Debug.WriteLine ("Validation:"); + Debug.WriteLine (info); + } + + GL.UseProgram (pgmId); + + GetUniformLocations (); + BindSamplesSlots (); + + Disable (); + } + + protected virtual void BindVertexAttributes() + { + GL.BindAttribLocation(pgmId, 0, "in_position"); + GL.BindAttribLocation(pgmId, 1, "in_tex"); + } + protected virtual void GetUniformLocations() + { + mvpLocation = GL.GetUniformLocation(pgmId, "mvp"); + } + protected virtual void BindSamplesSlots(){ + GL.Uniform1(GL.GetUniformLocation (pgmId, "tex"), 0); + } + public void SetMVP(Matrix4 _mvp){ + GL.UniformMatrix4(mvpLocation, false, ref _mvp); + } + public virtual void Enable(){ + GL.UseProgram (pgmId); + } + public virtual void Disable(){ + GL.UseProgram (0); + } + public static void Enable(Shader s) + { + if (s == null) + return; + s.Enable (); + } + public static void Disable(Shader s) + { + if (s == null) + return; + s.Disable (); + } + #endregion + + void loadSourcesFiles(){ + Stream s; + + if (!string.IsNullOrEmpty (VertSourcePath)) { + s = Crow.Interface.GetStreamFromPath (VertSourcePath); + if (s != null) { + using (StreamReader sr = new StreamReader (s)) { + vertSource = sr.ReadToEnd (); + } + } + } + + if (!string.IsNullOrEmpty (FragSourcePath)) { + s = Crow.Interface.GetStreamFromPath (FragSourcePath); + if (s != null) { + using (StreamReader sr = new StreamReader (s)) { + fragSource = sr.ReadToEnd (); + } + } + } + + if (!string.IsNullOrEmpty (GeomSourcePath)) { + s = Crow.Interface.GetStreamFromPath (GeomSourcePath); + if (s != null) { + using (StreamReader sr = new StreamReader (s)) { + geomSource = sr.ReadToEnd (); + } + } + } + } + void compileShader(int shader, string source) + { + GL.ShaderSource(shader, source); + GL.CompileShader(shader); + + string info; + GL.GetShaderInfoLog(shader, out info); + Debug.WriteLine(info); + + int compileResult; + GL.GetShader(shader, ShaderParameter.CompileStatus, out compileResult); + if (compileResult != 1) + { + Debug.WriteLine("Compile Error!"); + Debug.WriteLine(source); + } + } + public override string ToString () + { + return string.Format ("{0} {1} {2}", VertSourcePath, FragSourcePath, GeomSourcePath); + } + + #region IDisposable implementation + public virtual void Dispose () + { + if (GL.IsProgram (pgmId)) + GL.DeleteProgram (pgmId); + + if (GL.IsShader (vsId)) + GL.DeleteShader (vsId); + if (GL.IsShader (fsId)) + GL.DeleteShader (fsId); + if (GL.IsShader (gsId)) + GL.DeleteShader (gsId); + } + #endregion + } +} + diff --git a/OpenGL/Texture.cs b/OpenGL/Texture.cs new file mode 100644 index 0000000..ce24808 --- /dev/null +++ b/OpenGL/Texture.cs @@ -0,0 +1,94 @@ +// +// Texture.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2016 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Drawing; +using OpenTK.Graphics.OpenGL; +using System.Drawing.Imaging; +using System.IO; +using System.Diagnostics; + +namespace Crow +{ + public class Texture + { + public string Map; + public int texRef; + public int Width; + public int Height; + + public Texture(string _mapPath, bool flipY = true) + { + using (Stream s = Interface.GetStreamFromPath (_mapPath)) { + + try { + Map = _mapPath; + + Bitmap bitmap = new Bitmap (s); + + if (flipY) + bitmap.RotateFlip (RotateFlipType.RotateNoneFlipY); + + BitmapData data = bitmap.LockBits (new System.Drawing.Rectangle (0, 0, bitmap.Width, bitmap.Height), + ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + + createTexture (data.Scan0, data.Width, data.Height); + + bitmap.UnlockBits (data); + + GL.TexParameter (TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear); + GL.TexParameter (TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.GenerateMipmap (GenerateMipmapTarget.Texture2D); + + } catch (Exception ex) { + Debug.WriteLine ("Error loading texture: " + Map + ":" + ex.Message); + } + } + } + + public Texture(int width, int height) + { + createTexture (IntPtr.Zero, width, height); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Clamp); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Clamp); + } + + void createTexture(IntPtr data, int width, int height) + { + Width = width; + Height = height; + + GL.GenTextures(1, out texRef); + GL.BindTexture(TextureTarget.Texture2D, texRef); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, 0, + OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data); + } + + public static implicit operator int(Texture t) + { + return t == null ? 0: t.texRef; + } + } + +} diff --git a/OpenGL/vaoMesh.cs b/OpenGL/vaoMesh.cs new file mode 100644 index 0000000..19acd73 --- /dev/null +++ b/OpenGL/vaoMesh.cs @@ -0,0 +1,221 @@ +// +// vaoMesh.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2016 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using OpenTK; +using OpenTK.Graphics.OpenGL; + +namespace Crow +{ + public class vaoMesh : IDisposable + { + public int vaoHandle, + positionVboHandle, + texVboHandle, + eboHandle; + + public Vector3[] positions; + public Vector2[] texCoords; + public int[] indices; + + public vaoMesh() + { + } + + public vaoMesh (Vector3[] _positions, Vector2[] _texCoord, int[] _indices) + { + positions = _positions; + texCoords = _texCoord; + indices = _indices; + + CreateBuffers (); + } + + public vaoMesh (float x, float y, float z, float width, float height, float TileX = 1f, float TileY = 1f) + { + positions = + new Vector3[] { + new Vector3 (x - width / 2, y + height / 2, z), + new Vector3 (x - width / 2, y - height / 2, z), + new Vector3 (x + width / 2, y + height / 2, z), + new Vector3 (x + width / 2, y - height / 2, z) + }; + texCoords = new Vector2[] { + new Vector2 (0, TileY), + new Vector2 (0, 0), + new Vector2 (TileX, TileY), + new Vector2 (TileX, 0) + }; + indices = new int[] { 0, 1, 2, 3 }; + + CreateBuffers (); + } + public static vaoMesh CreateCube(){ + vaoMesh tmp = new vaoMesh (); + tmp.positions = new Vector3[] + { + new Vector3(-1.0f, -1.0f, -1.0f), + new Vector3( -1.0f, -1.0f, 1.0f), + new Vector3( 1.0f, -1.0f, -1.0f), + new Vector3(1.0f, -1.0f, 1.0f), + new Vector3(1.0f, 1.0f, -1.0f), + new Vector3( 1.0f, 1.0f, 1.0f), + new Vector3( -1.0f, 1.0f, -1.0f), + new Vector3(-1.0f, 1.0f, 1.0f) + }; + tmp.indices = new int[] + { + // front face + 0, 2, 1, 1, 2, 3, + // top face + 2, 4, 3, 3, 4, 5, + // back face + 4, 6, 5, 5, 6, 7, + // left face + 6, 0, 7, 7, 0, 1, + // bottom face + 1, 3, 7, 7, 3, 5, + // right face +// 1, 5, 6, 6, 2, 1, + }; + tmp.texCoords = new Vector2[] + { + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 0), + new Vector2(1, 1), + new Vector2(0, 0), + new Vector2(0, 1), + new Vector2(1, 0), + new Vector2(1, 1), + }; + tmp.CreateBuffers (); + return tmp; +// Normals = new Vector3[] +// { +// new Vector3(-1.0f, -1.0f, 1.0f), +// new Vector3( 1.0f, -1.0f, 1.0f), +// new Vector3( 1.0f, 1.0f, 1.0f), +// new Vector3(-1.0f, 1.0f, 1.0f), +// new Vector3(-1.0f, -1.0f, -1.0f), +// new Vector3( 1.0f, -1.0f, -1.0f), +// new Vector3( 1.0f, 1.0f, -1.0f), +// new Vector3(-1.0f, 1.0f, -1.0f), +// }; +// +// Colors = new int[] +// { +// Utilities.ColorToRgba32(Color.DarkRed), +// Utilities.ColorToRgba32(Color.DarkRed), +// Utilities.ColorToRgba32(Color.Gold), +// Utilities.ColorToRgba32(Color.Gold), +// Utilities.ColorToRgba32(Color.DarkRed), +// Utilities.ColorToRgba32(Color.DarkRed), +// Utilities.ColorToRgba32(Color.Gold), +// Utilities.ColorToRgba32(Color.Gold), +// }; + } + public void CreateBuffers(){ + CreateVBOs (); + CreateVAOs (); + } + protected void CreateVBOs() + { + positionVboHandle = GL.GenBuffer(); + GL.BindBuffer(BufferTarget.ArrayBuffer, positionVboHandle); + GL.BufferData(BufferTarget.ArrayBuffer, + new IntPtr(positions.Length * Vector3.SizeInBytes), + positions, BufferUsageHint.StaticDraw); + + if (texCoords != null) { + texVboHandle = GL.GenBuffer (); + GL.BindBuffer (BufferTarget.ArrayBuffer, texVboHandle); + GL.BufferData (BufferTarget.ArrayBuffer, + new IntPtr (texCoords.Length * Vector2.SizeInBytes), + texCoords, BufferUsageHint.StaticDraw); + } + + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + + if (indices != null) { + eboHandle = GL.GenBuffer (); + GL.BindBuffer (BufferTarget.ElementArrayBuffer, eboHandle); + GL.BufferData (BufferTarget.ElementArrayBuffer, + new IntPtr (sizeof(uint) * indices.Length), + indices, BufferUsageHint.StaticDraw); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); + } + } + protected void CreateVAOs() + { + vaoHandle = GL.GenVertexArray(); + GL.BindVertexArray(vaoHandle); + + GL.EnableVertexAttribArray(0); + GL.BindBuffer(BufferTarget.ArrayBuffer, positionVboHandle); + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, true, Vector3.SizeInBytes, 0); + + if (texCoords != null) { + GL.EnableVertexAttribArray (1); + GL.BindBuffer (BufferTarget.ArrayBuffer, texVboHandle); + GL.VertexAttribPointer (1, 2, VertexAttribPointerType.Float, true, Vector2.SizeInBytes, 0); + } + if (indices != null) + GL.BindBuffer(BufferTarget.ElementArrayBuffer, eboHandle); + + GL.BindVertexArray(0); + } + + public void Render(BeginMode _primitiveType){ + GL.BindVertexArray(vaoHandle); + if (indices == null) + GL.DrawArrays (_primitiveType, 0, positions.Length); + else + GL.DrawElements(_primitiveType, indices.Length, + DrawElementsType.UnsignedInt, IntPtr.Zero); + GL.BindVertexArray (0); + } + public void Render(BeginMode _primitiveType, int[] _customIndices){ + GL.BindVertexArray(vaoHandle); + GL.DrawElements(_primitiveType, _customIndices.Length, + DrawElementsType.UnsignedInt, _customIndices); + GL.BindVertexArray (0); + } + public void Render(BeginMode _primitiveType, int instances){ + + GL.BindVertexArray(vaoHandle); + GL.DrawElementsInstanced(_primitiveType, indices.Length, + DrawElementsType.UnsignedInt, IntPtr.Zero, instances); + GL.BindVertexArray (0); + } + + #region IDisposable implementation + public void Dispose () + { + GL.DeleteBuffer (positionVboHandle); + GL.DeleteBuffer (texVboHandle); + GL.DeleteBuffer (eboHandle); + GL.DeleteVertexArray (vaoHandle); + } + #endregion + + } +} \ No newline at end of file diff --git a/src/CrowEdit.cs b/src/CrowEdit.cs new file mode 100644 index 0000000..1e0323f --- /dev/null +++ b/src/CrowEdit.cs @@ -0,0 +1,228 @@ +// +// Main.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2017 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +using Crow; +using System.IO; +using System.Collections.Generic; + +namespace CrowEdit +{ + public class CrowEdit : CrowWindow + { + public Command CMDNew, CMDOpen, CMDSave, CMDSaveAs, CMDQuit, CMDUndo, CMDRedo, CMDCut, CMDCopy, CMDPaste, CMDHelp, CMDAbout; + + string _curDir = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments); + string _curFilePath = "unamed.txt"; + string _text = "", _origText=""; + + List undoStack = new List(); + List redoStack = new List(); + + public string Text { + get { return _text; } + set { + if (_text == value) + return; + undoStack.Add (_text); + CMDUndo.CanExecute = true; + redoStack.Clear (); + CMDRedo.CanExecute = false; + _text = value; + NotifyValueChanged ("Text", _text); + NotifyValueChanged ("IsDirty", IsDirty); + } + } + bool isDirty = false; + public bool IsDirty { get { return _text != _origText; }} + + public string CurrentDir { + get { return _curDir; } + set { + if (_curDir == value) + return; + _curDir = value; + NotifyValueChanged ("CurrentDir", _curDir); + } + } + public string CurFilePath { + get { return _curFilePath; } + set { + if (_curFilePath == value) + return; + _curFilePath = value; + NotifyValueChanged ("CurFilePath", _curFilePath); + } + } + public string CurFileFullPath { get { return Path.Combine(CurrentDir,CurFilePath); }} + + void initCommands(){ + CMDNew = new Command(new Action(() => newFile())) { Caption = "New", Icon = new SvgPicture("#CrowEdit.ui.icons.blank-file.svg")}; + CMDOpen = new Command(new Action(() => openFileDialog())) { Caption = "Open...", Icon = new SvgPicture("#CrowEdit.ui.icons.outbox.svg")}; + CMDSave = new Command(new Action(() => saveFileDialog())) { Caption = "Save", Icon = new SvgPicture("#CrowEdit.ui.icons.inbox.svg"), CanExecute = false}; + CMDSaveAs = new Command(new Action(() => saveFileDialog())) { Caption = "Save As...", Icon = new SvgPicture("#CrowEdit.ui.icons.inbox.svg"), CanExecute = false}; + CMDQuit = new Command(new Action(() => Quit (null, null))) { Caption = "Quit", Icon = new SvgPicture("#CrowEdit.ui.icons.sign-out.svg")}; + CMDUndo = new Command(new Action(() => undo())) { Caption = "Undo", Icon = new SvgPicture("#CrowEdit.ui.icons.reply.svg"), CanExecute = false}; + CMDRedo = new Command(new Action(() => redo())) { Caption = "Redo", Icon = new SvgPicture("#CrowEdit.ui.icons.share-arrow.svg"), CanExecute = false}; + CMDCut = new Command(new Action(() => Quit (null, null))) { Caption = "Cut", Icon = new SvgPicture("#CrowEdit.ui.icons.scissors.svg"), CanExecute = false}; + CMDCopy = new Command(new Action(() => Quit (null, null))) { Caption = "Copy", Icon = new SvgPicture("#CrowEdit.ui.icons.copy-file.svg"), CanExecute = false}; + CMDPaste = new Command(new Action(() => Quit (null, null))) { Caption = "Paste", Icon = new SvgPicture("#CrowEdit.ui.icons.paste-on-document.svg"), CanExecute = false}; + CMDHelp = new Command(new Action(() => System.Diagnostics.Debug.WriteLine("help"))) { Caption = "Help", Icon = new SvgPicture("#CrowEdit.ui.icons.question.svg")}; + + } + void newFile(){ + CurFilePath = "unamed.txt"; + _origText = _text = ""; + NotifyValueChanged ("Text", _text); + NotifyValueChanged ("IsDirty", false); + redoStack.Clear (); + undoStack.Clear (); + CMDRedo.CanExecute = false; + CMDUndo.CanExecute = false; + NotifyValueChanged ("CurFileFullPath", CurFileFullPath); + } + void undo(){ + string step = undoStack [undoStack.Count -1]; + redoStack.Add (_text); + CMDRedo.CanExecute = true; + undoStack.RemoveAt(undoStack.Count -1); + + _text = step; + + NotifyValueChanged ("Text", _text); + NotifyValueChanged ("IsDirty", IsDirty); + + if (undoStack.Count == 0) + CMDUndo.CanExecute = false; + } + void redo(){ + string step = redoStack [redoStack.Count -1]; + undoStack.Add (_text); + CMDUndo.CanExecute = true; + redoStack.RemoveAt(redoStack.Count -1); + _text = step; + NotifyValueChanged ("Text", _text); + NotifyValueChanged ("IsDirty", IsDirty); + + if (redoStack.Count == 0) + CMDRedo.CanExecute = false; + } + void openFileDialog(){ + Load ("#CrowEdit.ui.openFile.crow").DataSource = this; + } + void saveFileDialog(){ + Load ("#CrowEdit.ui.saveFile.crow").DataSource = this; + } + void openFileDialog_OkClicked (object sender, EventArgs e) + { + FileDialog fd = sender as FileDialog; + if (string.IsNullOrEmpty (fd.SelectedFile)) + return; + CurFilePath = fd.SelectedFile; + CurrentDir = fd.SelectedDirectory; + + using (StreamReader sr = new StreamReader (CurFileFullPath)) { + _origText = sr.ReadToEnd (); + } + _text = _origText; + + NotifyValueChanged ("Text", _text); + NotifyValueChanged ("IsDirty", false); + redoStack.Clear (); + undoStack.Clear (); + CMDRedo.CanExecute = false; + CMDUndo.CanExecute = false; + + NotifyValueChanged ("CurFileFullPath", CurFileFullPath); + } + void saveFileDialog_OkClicked (object sender, EventArgs e) + { + FileDialog fd = sender as FileDialog; + + if (!string.IsNullOrEmpty (fd.SelectedFile)) + CurFilePath = fd.SelectedFile; + CurrentDir = fd.SelectedDirectory; + + System.Diagnostics.Debug.WriteLine (CurFileFullPath); +// using (StreamWriter sr = new StreamWriter (fd.SelectedFile)) { +// sr.Write(_text); +// } + _origText = _text; + + NotifyValueChanged ("IsDirty", false); + NotifyValueChanged ("CurFileFullPath", CurFileFullPath); + } + void onTextChanged (object sender, TextChangeEventArgs e) + { + System.Diagnostics.Debug.WriteLine ("text changed"); + NotifyValueChanged ("IsDirty", IsDirty); + } + + [STAThread] + static void Main () + { + using (CrowEdit win = new CrowEdit ()) { + win.Run (30); + } + } + public CrowEdit () + : base(800, 600,"Crow Simple Editor") + {} + + protected override void OnLoad (EventArgs e) + { + base.OnLoad (e); + + this.ValueChanged += CrowEdit_ValueChanged; + initCommands (); + + Load ("#CrowEdit.ui.main.crow").DataSource = this; + NotifyValueChanged ("CurFileFullPath", CurFileFullPath); + } + + void textView_KeyDown (object sender, Crow.KeyboardKeyEventArgs e) + { + if (e.Control) { + if (e.Key == Key.W) { + if (e.Shift) + CMDRedo.Execute (); + else + CMDUndo.Execute (); + } + } + } + + void CrowEdit_ValueChanged (object sender, ValueChangeEventArgs e) + { + if (e.MemberName == "IsDirty" && isDirty != (bool)e.NewValue) { + isDirty = (bool)e.NewValue; + if (isDirty) { + CMDSave.CanExecute = true; + CMDSaveAs.CanExecute = true; + }else{ + CMDSave.CanExecute = false; + CMDSaveAs.CanExecute = false; + } + } + } + + } +} + diff --git a/src/CrowEditExtentions.cs b/src/CrowEditExtentions.cs new file mode 100644 index 0000000..a2d7ac6 --- /dev/null +++ b/src/CrowEditExtentions.cs @@ -0,0 +1,41 @@ +// +// CrowEditExtentions.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2017 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +using System.Collections.Generic; + +namespace CrowEdit +{ + public static class CrowEditExtentions + { + public static List AllIndexesOf(this string str, string value) { + if (String.IsNullOrEmpty(value)) + throw new ArgumentException("the string to find may not be empty", "value"); + List indexes = new List(); + for (int index = 0;; index += value.Length) { + index = str.IndexOf(value, index); + if (index == -1) + return indexes; + indexes.Add(index); + } + } + } +} + diff --git a/src/CrowWindow.cs b/src/CrowWindow.cs new file mode 100644 index 0000000..b1308d1 --- /dev/null +++ b/src/CrowWindow.cs @@ -0,0 +1,370 @@ +// +// CrowWindow.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Threading; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using System.Collections.Generic; + +namespace Crow +{ + public class CrowWindow : GameWindow, IValueChange + { + #region IValueChange implementation + public event EventHandler ValueChanged; + public virtual void NotifyValueChanged(string MemberName, object _value) + { + if (ValueChanged != null) + ValueChanged.Invoke(this, new ValueChangeEventArgs(MemberName, _value)); + } + #endregion + + #region FPS + int frameCpt = 0; + int _fps = 0; + + public int fps { + get { return _fps; } + set { + if (_fps == value) + return; + + _fps = value; + #if MEASURE_TIME + if (_fps > fpsMax) { + fpsMax = _fps; + ValueChanged.Raise(this, new ValueChangeEventArgs ("fpsMax", fpsMax)); + } else if (_fps < fpsMin) { + fpsMin = _fps; + ValueChanged.Raise(this, new ValueChangeEventArgs ("fpsMin", fpsMin)); + } + #endif + if (frameCpt % 3 == 0) + ValueChanged.Raise(this, new ValueChangeEventArgs ("fps", _fps)); + #if MEASURE_TIME +// foreach (PerformanceMeasure m in PerfMeasures) +// m.NotifyChanges(); + #endif + } + } + + #if MEASURE_TIME + public PerformanceMeasure glDrawMeasure = new PerformanceMeasure("OpenGL Draw", 10); + + public int fpsMin = int.MaxValue; + public int fpsMax = 0; + + void resetFps () + { + fpsMin = int.MaxValue; + fpsMax = 0; + _fps = 0; + } + #endif + + #endregion + + #region ctor + public CrowWindow(int _width = 800, int _height = 600, string _title="Crow", + int colors = 32, int depth = 24, int stencil = 0, int samples = 1, + int major=3, int minor=3) + : this(_width, _height, new OpenTK.Graphics.GraphicsMode(colors, depth, stencil, samples), + _title,GameWindowFlags.Default,DisplayDevice.Default, + major,minor,OpenTK.Graphics.GraphicsContextFlags.Default) + { + } + public CrowWindow (int width, int height, OpenTK.Graphics.GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device, int major, int minor, OpenTK.Graphics.GraphicsContextFlags flags) + : base(width,height,mode,title,options,device,major,minor,flags) + { + } + + #endregion + + protected Shader shader; + public List ifaceControl = new List(); + int focusedIdx = -1, activeIdx = -2; + + void addInterfaceControler(InterfaceControler ifaceControler) + { + ifaceControler.CrowInterface.Quit += Quit; + ifaceControler.CrowInterface.MouseCursorChanged += CrowInterface_MouseCursorChanged; + + ifaceControl.Add (ifaceControler); + } + void openGLDraw(){ + //save GL states + bool blend, depthTest, cullFace; + GL.GetBoolean (GetPName.Blend, out blend); + GL.GetBoolean (GetPName.DepthTest, out depthTest); + GL.GetBoolean (GetPName.CullFace, out cullFace); + GL.Enable (EnableCap.Blend); + GL.Disable (EnableCap.DepthTest); + GL.Disable (EnableCap.CullFace); + + #if MEASURE_TIME + glDrawMeasure.StartCycle(); + #endif + + shader.Enable (); + for (int i = 0; i < ifaceControl.Count; i++) { + shader.SetMVP (ifaceControl [i].InterfaceMVP); + ifaceControl [i].OpenGLDraw (); + } + + #if MEASURE_TIME + glDrawMeasure.StopCycle(); + #endif + + //restore GL states + if (!blend) + GL.Disable (EnableCap.Blend); + if (depthTest) + GL.Enable (EnableCap.DepthTest); + if (cullFace) + GL.Enable (EnableCap.CullFace); + } + + public void Quit (object sender, EventArgs e) + { + foreach (InterfaceControler ic in ifaceControl) { + ic.Dispose (); + } + this.Exit (); + } + void CrowInterface_MouseCursorChanged (object sender, MouseCursorChangedEventArgs e) + { + this.Cursor = new MouseCursor( + (int)e.NewCursor.Xhot, + (int)e.NewCursor.Yhot, + (int)e.NewCursor.Width, + (int)e.NewCursor.Height, + e.NewCursor.data); + } + + #region Events + //those events are raised only if mouse isn't in a graphic object + public event EventHandler MouseWheelChanged; + public event EventHandler MouseButtonUp; + public event EventHandler MouseButtonDown; + public event EventHandler MouseClick; + public event EventHandler MouseMove; + public event EventHandler KeyboardKeyDown; + public event EventHandler KeyboardKeyUp; + + #endregion + + public ProjectiveIFaceControler Add3DInterface(int width, int height, Matrix4 ifaceModelMat){ + ProjectiveIFaceControler tmp = new ProjectiveIFaceControler (new Rectangle (0, 0, width, height), ifaceModelMat); + addInterfaceControler (tmp); + return tmp; + } + public GraphicObject AddWidget (GraphicObject g, int interfaceIdx = 0){ + if (ifaceControl.Count == 0)//create default orthogonal interface + addInterfaceControler (new InterfaceControler ( + new Rectangle (0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height))); + ifaceControl [interfaceIdx].CrowInterface.AddWidget (g); + return g; + } + public void DeleteWidget (GraphicObject g, int interfaceIdx = 0){ + ifaceControl [interfaceIdx].CrowInterface.DeleteWidget (g); + } + public GraphicObject Load (string path, int interfaceIdx = 0){ + if (ifaceControl.Count == 0)//create default orthogonal interface + addInterfaceControler (new InterfaceControler ( + new Rectangle (0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height))); + return ifaceControl [interfaceIdx].CrowInterface.LoadInterface (path); + } + public GraphicObject FindByName (string nameToFind){ + for (int i = 0; i < ifaceControl.Count; i++) { + GraphicObject tmp = ifaceControl [i].CrowInterface.FindByName (nameToFind); + if (tmp != null) + return tmp; + } + return null; + } + public void ClearInterface (int interfaceIdx = 0){ + ifaceControl [interfaceIdx].CrowInterface.ClearInterface (); + } + /// Override this method for your OpenGL rendering calls + public virtual void OnRender(FrameEventArgs e) + { + } + /// Override this method to customize clear method between frames + public virtual void GLClear() + { + GL.Clear (ClearBufferMask.ColorBufferBit|ClearBufferMask.DepthBufferBit); + } + + #region Game win overrides + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + + this.KeyPress += new EventHandler(OpenTKGameWindow_KeyPress); + Keyboard.KeyDown += new EventHandler(Keyboard_KeyDown); + Keyboard.KeyUp += new EventHandler(Keyboard_KeyUp); + Mouse.WheelChanged += new EventHandler(GL_Mouse_WheelChanged); + Mouse.ButtonDown += new EventHandler(GL_Mouse_ButtonDown); + Mouse.ButtonUp += new EventHandler(GL_Mouse_ButtonUp); + Mouse.Move += new EventHandler(GL_Mouse_Move); + + #if DEBUG + Console.WriteLine("\n\n*************************************"); + Console.WriteLine("GL version: " + GL.GetString (StringName.Version)); + Console.WriteLine("GL vendor: " + GL.GetString (StringName.Vendor)); + Console.WriteLine("GLSL version: " + GL.GetString (StringName.ShadingLanguageVersion)); + Console.WriteLine("*************************************\n"); + #endif + + shader = new Shader (); + shader.Enable (); + + GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f); + } + protected override void OnUpdateFrame(FrameEventArgs e) + { + base.OnUpdateFrame(e); + fps = (int)RenderFrequency; + + #if MEASURE_TIME + if (frameCpt > 500) { + resetFps (); + frameCpt = 0; +// #if DEBUG +// GC.Collect(); +// GC.WaitForPendingFinalizers(); +// NotifyValueChanged("memory", GC.GetTotalMemory (false).ToString()); +// #endif + } + #endif + + frameCpt++; + } + protected override void OnRenderFrame(FrameEventArgs e) + { + GLClear (); + + base.OnRenderFrame(e); + + OnRender (e); + openGLDraw (); + + + SwapBuffers (); + } + protected override void OnResize(EventArgs e) + { + base.OnResize (e); + for (int i = 0; i < ifaceControl.Count; i++) { + ifaceControl[i].ProcessResize( + new Rectangle( + 0, + 0, + this.ClientRectangle.Width, + this.ClientRectangle.Height)); + } + } + #endregion + + #region Mouse and Keyboard Handling + void update_mouseButtonStates(ref MouseState e, OpenTK.Input.MouseState otk_e){ + for (int i = 0; i < MouseState.MaxButtons; i++) { + if (otk_e.IsButtonDown ((OpenTK.Input.MouseButton)i)) + e.EnableBit (i); + } + } + protected virtual void GL_Mouse_Move(object sender, OpenTK.Input.MouseMoveEventArgs otk_e) + { + if (activeIdx == -2) { + focusedIdx = -1; + for (int i = 0; i < ifaceControl.Count; i++) { + if (ifaceControl [i].ProcessMouseMove (otk_e.X, otk_e.Y)) { + focusedIdx = i; + return; + } + } + } else if (focusedIdx >= 0) { + ifaceControl [focusedIdx].ProcessMouseMove (otk_e.X, otk_e.Y); + return; + } + if (focusedIdx < 0) + MouseMove.Raise (sender, otk_e); + } + protected virtual void GL_Mouse_ButtonUp(object sender, OpenTK.Input.MouseButtonEventArgs otk_e) + { + activeIdx = -2; + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessMouseButtonUp ((int)otk_e.Button)) + return; + } + MouseButtonUp.Raise (sender, otk_e); + } + protected virtual void GL_Mouse_ButtonDown(object sender, OpenTK.Input.MouseButtonEventArgs otk_e) + { + activeIdx = focusedIdx; + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessMouseButtonDown ((int)otk_e.Button)) + return; + } + MouseButtonDown.Raise (sender, otk_e); + } + protected virtual void GL_Mouse_WheelChanged(object sender, OpenTK.Input.MouseWheelEventArgs otk_e) + { + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessMouseWheelChanged (otk_e.DeltaPrecise)) + return; + } + MouseWheelChanged.Raise (sender, otk_e); + } + + protected virtual void Keyboard_KeyDown(object sender, OpenTK.Input.KeyboardKeyEventArgs otk_e) + { + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessKeyDown((int)otk_e.Key)) + return; + } + KeyboardKeyDown.Raise (this, otk_e); + } + protected virtual void Keyboard_KeyUp(object sender, OpenTK.Input.KeyboardKeyEventArgs otk_e) + { + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessKeyUp((int)otk_e.Key)) + return; + } + KeyboardKeyUp.Raise (this, otk_e); + } + protected virtual void OpenTKGameWindow_KeyPress (object sender, OpenTK.KeyPressEventArgs e) + { + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessKeyPress (e.KeyChar)) + return; + } + //TODO:create keyboardkeypress evt + } + #endregion + } +} diff --git a/src/InterfaceControler.cs b/src/InterfaceControler.cs new file mode 100644 index 0000000..4d988c1 --- /dev/null +++ b/src/InterfaceControler.cs @@ -0,0 +1,267 @@ +// +// InterfaceControler.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using System.Threading; +using System.Collections.Generic; + +namespace Crow +{ + public class ProjectiveIFaceControler : InterfaceControler { + Matrix4 modelview; + int[] viewport = new int[4]; + Vector3 vEyePosition; + + public Matrix4 ifaceModelMat; + Point localMousePos; + + public ProjectiveIFaceControler(Rectangle ifaceBounds, Matrix4 _ifaceModelMat) + : base(ifaceBounds){ + ifaceModelMat = _ifaceModelMat; + } + + public override Matrix4 InterfaceMVP { + get { return ifaceModelMat * modelview * projection; } + } + + public override void initGL(){ + quad = new Crow.vaoMesh (0, 0, 0, 1, 1, 1, -1); + //ifaceModelMat = Matrix4.CreateRotationX(MathHelper.PiOver2) * Matrix4.CreateTranslation(Vector3.UnitY); + CrowInterface.ProcessResize(iRect); + createContext (); + //CrowInterface.ProcessResize (iRect); + } + public override void ProcessResize (Rectangle newSize) + { + } + public override void OpenGLDraw () + { + GL.Enable (EnableCap.DepthTest); + base.OpenGLDraw (); + GL.Disable (EnableCap.DepthTest); + } + public void UpdateView (Matrix4 _projection, Matrix4 _modelview, int[] _viewport, Vector3 _vEyePosition) + { + projection = _projection; + modelview = _modelview; + viewport = _viewport; + vEyePosition = _vEyePosition; + } + public override bool ProcessMouseMove (int x, int y) + { + Matrix4 mv = ifaceModelMat * modelview; + Vector3 vMouse = UnProject(ref projection, ref mv, viewport, new Vector2 (x, y)).Xyz; + Vector3 vE = vEyePosition.Transform (ifaceModelMat.Inverted()); + Vector3 vMouseRay = Vector3.Normalize(vMouse - vE); + float a = vE.Z / vMouseRay.Z; + vMouse = vE - vMouseRay * a; + //vMouse = vMouse.Transform (interfaceModelView.Inverted()); + localMousePos = new Point ((int)Math.Truncate ((vMouse.X + 0.5f) * iRect.Width), + iRect.Height - (int)Math.Truncate ((vMouse.Y + 0.5f) * iRect.Height)); + mouseIsInInterface = localMousePos.X.IsInBetween (0, iRect.Width) & localMousePos.Y.IsInBetween (0, iRect.Height); + + return mouseIsInInterface ? CrowInterface.ProcessMouseMove (localMousePos.X, localMousePos.Y) : false; + } + Vector4 UnProject(ref Matrix4 projection, ref Matrix4 view, int[] viewport, Vector2 mouse) + { + Vector4 vec; + + vec.X = 2.0f * mouse.X / (float)viewport[2] - 1; + vec.Y = -(2.0f * mouse.Y / (float)viewport[3] - 1); + vec.Z = 0f; + vec.W = 1.0f; + + Matrix4 viewInv = Matrix4.Invert(view); + Matrix4 projInv = Matrix4.Invert(projection); + + Vector4.Transform(ref vec, ref projInv, out vec); + Vector4.Transform(ref vec, ref viewInv, out vec); + + if (vec.W > float.Epsilon || vec.W < float.Epsilon) + { + vec.X /= vec.W; + vec.Y /= vec.W; + vec.Z /= vec.W; + } + + return vec; + } + } + public class InterfaceControler : IDisposable { + public Interface CrowInterface; + public int texID; + public vaoMesh quad; + public Rectangle iRect = new Rectangle(0,0,2048,2048); + public bool mouseIsInInterface = false; + + protected Matrix4 projection; + public virtual Matrix4 InterfaceMVP { + get { return projection; } + } + + #if MEASURE_TIME + public List PerfMeasures; + public PerformanceMeasure glDrawMeasure = new PerformanceMeasure("OpenGL Draw", 10); + #endif + + #region CTOR + public InterfaceControler(Rectangle ifaceBounds){ + iRect = ifaceBounds; + + CrowInterface = new Interface (); + + #if MEASURE_TIME + PerfMeasures = new List ( + new PerformanceMeasure[] { + this.CrowInterface.updateMeasure, + this.CrowInterface.layoutingMeasure, + this.CrowInterface.clippingMeasure, + this.CrowInterface.drawingMeasure, + this.glDrawMeasure + } + ); + #endif + + Thread t = new Thread (interfaceThread); + t.IsBackground = true; + t.Start (); + + initGL (); + } + #endregion + + void interfaceThread() + { + while (CrowInterface.ClientRectangle.Size.Width == 0) + Thread.Sleep (5); + + while (true) { + CrowInterface.Update (); + //Thread.Sleep (1); + } + } + + #region Mouse And Keyboard handling + public virtual void ProcessResize(Rectangle newSize){ + iRect = newSize; + CrowInterface.ProcessResize(newSize); + createContext (); + GL.Viewport (0, 0, newSize.Width, newSize.Height);//TODO:find a better place for this + } + public virtual bool ProcessMouseMove(int x, int y){ + return CrowInterface.ProcessMouseMove (x, y); + } + public virtual bool ProcessMouseButtonUp(int button) + { + return CrowInterface.ProcessMouseButtonUp (button); + } + public virtual bool ProcessMouseButtonDown(int button) + { + return CrowInterface.ProcessMouseButtonDown (button); + } + public virtual bool ProcessMouseWheelChanged(float delta) + { + return CrowInterface.ProcessMouseWheelChanged (delta); + } + public virtual bool ProcessKeyDown(int Key){ + return CrowInterface.ProcessKeyDown(Key); + } + public virtual bool ProcessKeyUp(int Key){ + return CrowInterface.ProcessKeyUp(Key); + } + public virtual bool ProcessKeyPress(char Key){ + return CrowInterface.ProcessKeyPress(Key); + } + #endregion + + #region graphic context + public virtual void initGL(){ + projection = OpenTK.Matrix4.CreateOrthographicOffCenter (-0.5f, 0.5f, -0.5f, 0.5f, 1, -1); + quad = new Crow.vaoMesh (0, 0, 0, 1, 1, 1, -1); + createContext (); + } + /// Create the texture for the interface redering + public virtual void createContext() + { + if (GL.IsTexture(texID)) + GL.DeleteTexture (texID); + GL.GenTextures(1, out texID); + GL.ActiveTexture (TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, texID); + + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, + iRect.Width, iRect.Height, 0, + OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, CrowInterface.bmp); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.BindTexture(TextureTarget.Texture2D, 0); + } + /// Rendering of the interface + public virtual void OpenGLDraw() + { + #if MEASURE_TIME + glDrawMeasure.StartCycle(); + #endif + + GL.ActiveTexture (TextureUnit.Texture0); + GL.BindTexture (TextureTarget.Texture2D, texID); + if (Monitor.TryEnter(CrowInterface.RenderMutex)) { + if (CrowInterface.IsDirty) { + GL.TexSubImage2D (TextureTarget.Texture2D, 0, + CrowInterface.DirtyRect.Left, CrowInterface.DirtyRect.Top, + CrowInterface.DirtyRect.Width, CrowInterface.DirtyRect.Height, + OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, CrowInterface.dirtyBmp); + CrowInterface.IsDirty = false; + } + Monitor.Exit (CrowInterface.RenderMutex); + } + quad.Render (BeginMode.TriangleStrip); + GL.BindTexture(TextureTarget.Texture2D, 0); + + #if MEASURE_TIME + glDrawMeasure.StopCycle(); + #endif + } + #endregion + + #region IDisposable implementation + + public void Dispose () + { + if (GL.IsTexture(texID)) + GL.DeleteTexture (texID); + if (quad != null) + quad.Dispose (); + } + + #endregion + } +} + diff --git a/src/ScrollingObject.cs b/src/ScrollingObject.cs new file mode 100644 index 0000000..f0463ae --- /dev/null +++ b/src/ScrollingObject.cs @@ -0,0 +1,180 @@ +// +// ScrollingObject.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Xml.Serialization; +using System.ComponentModel; +using System.Collections; +using Cairo; + + +namespace Crow +{ + public class ScrollingObject : GraphicObject + { + #region CTOR + public ScrollingObject ():base() + { + } + #endregion + + int scrollX, scrollY, maxScrollX, maxScrollY, mouseWheelSpeed; + + /// Horizontal Scrolling Position + [XmlAttributeAttribute][DefaultValue(0)] + public virtual int ScrollX { + get { return scrollX; } + set { + if (scrollX == value) + return; + + int newS = value; + if (newS < 0) + newS = 0; + else if (newS > maxScrollX) + newS = maxScrollX; + + if (newS == scrollX) + return; + + scrollX = value; + + NotifyValueChanged ("ScrollX", scrollX); + RegisterForGraphicUpdate (); + } + } + /// Vertical Scrolling Position + [XmlAttributeAttribute][DefaultValue(0)] + public virtual int ScrollY { + get { return scrollY; } + set { + if (scrollY == value) + return; + + int newS = value; + if (newS < 0) + newS = 0; + else if (newS > maxScrollY) + newS = maxScrollY; + + if (newS == scrollY) + return; + + scrollY = value; + + NotifyValueChanged ("ScrollY", scrollY); + RegisterForGraphicUpdate (); + } + } + /// Horizontal Scrolling maximum value + [XmlAttributeAttribute][DefaultValue(0)] + public virtual int MaxScrollX { + get { return maxScrollX; } + set { + if (maxScrollX == value) + return; + + maxScrollX = value; + + if (scrollX > maxScrollX) + ScrollX = maxScrollX; + + NotifyValueChanged ("MaxScrollX", maxScrollX); + RegisterForGraphicUpdate (); + } + } + /// Vertical Scrolling maximum value + [XmlAttributeAttribute][DefaultValue(0)] + public virtual int MaxScrollY { + get { return maxScrollY; } + set { + if (maxScrollY == value) + return; + + maxScrollY = value; + + if (scrollY > maxScrollY) + ScrollY = maxScrollY; + + NotifyValueChanged ("MaxScrollY", maxScrollY); + RegisterForGraphicUpdate (); + } + } + /// Mouse Wheel Scrolling multiplier + [XmlAttributeAttribute][DefaultValue(1)] + public virtual int MouseWheelSpeed { + get { return mouseWheelSpeed; } + set { + if (mouseWheelSpeed == value) + return; + + mouseWheelSpeed = value; + + NotifyValueChanged ("MouseWheelSpeed", mouseWheelSpeed); + } + } + + /// Process scrolling vertically, or if shift is down, vertically + public override void onMouseWheel (object sender, MouseWheelEventArgs e) + { + base.onMouseWheel (sender, e); + if (CurrentInterface.Keyboard.IsKeyDown (Key.ShiftLeft)) + ScrollX -= e.Delta * MouseWheelSpeed; + else + ScrollY -= e.Delta * MouseWheelSpeed; + + } + /// Process scrolling with arrow keys, home and end keys. + public override void onKeyDown (object sender, KeyboardKeyEventArgs e) + { + base.onKeyDown (sender, e); + + switch (e.Key) { + case Key.Up: + ScrollY--; + break; + case Key.Down: + ScrollY++; + break; + case Key.Left: + ScrollX--; + break; + case Key.Right: + ScrollX++; + break; + case Key.Home: + ScrollX = 0; + ScrollY = 0; + break; + case Key.End: + ScrollX = MaxScrollX; + ScrollY = MaxScrollY; + break; + } + } + } +} + diff --git a/src/ScrollingTextBox.cs b/src/ScrollingTextBox.cs new file mode 100644 index 0000000..a9b429a --- /dev/null +++ b/src/ScrollingTextBox.cs @@ -0,0 +1,768 @@ +// +// ScrollingTextBox.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Xml.Serialization; +using System.ComponentModel; +using System.Collections; +using Cairo; +using System.Text; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Linq; + +namespace Crow +{ + /// + /// Scrolling text box optimized for monospace fonts, for coding + /// + public class ScrollingTextBox : ScrollingObject + { + #region CTOR + public ScrollingTextBox ():base() + { + + + } + #endregion + + public event EventHandler TextChanged; + + public virtual void OnTextChanged(Object sender, EventArgs e) + { + TextChanged.Raise (this, e); + } + + #region private and protected fields + string lineBreak = Interface.LineBreak; + int visibleLines = 1; + List lines; + string _text = "label"; + Color selBackground; + Color selForeground; + Point mouseLocalPos = 0;//mouse coord in widget space + int _currentCol; //0 based cursor position in string + int _currentLine; + Point _selBegin = -1; //selection start (row,column) + Point _selRelease = -1; //selection end (row,column) + + protected Rectangle rText; + protected FontExtents fe; + protected TextExtents te; + #endregion + + [XmlAttributeAttribute][DefaultValue("label")] + public string Text + { + get { + return lines == null ? + _text : lines.Aggregate((i, j) => i + Interface.LineBreak + j); + } + set + { + if (string.Equals (value, _text, StringComparison.Ordinal)) + return; + + _text = value; + + if (string.IsNullOrEmpty(_text)) + _text = ""; + + lines = getLines; + MaxScrollY = Math.Max (0, lines.Count - visibleLines); + + OnTextChanged (this, null); + RegisterForGraphicUpdate (); + } + } + + + [XmlAttributeAttribute][DefaultValue("BlueGray")] + public virtual Color SelectionBackground { + get { return selBackground; } + set { + if (value == selBackground) + return; + selBackground = value; + NotifyValueChanged ("SelectionBackground", selBackground); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute][DefaultValue("White")] + public virtual Color SelectionForeground { + get { return selForeground; } + set { + if (value == selForeground) + return; + selForeground = value; + NotifyValueChanged ("SelectionForeground", selForeground); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute][DefaultValue(0)] + public int CurrentColumn{ + get { return _currentCol; } + set { + if (value == _currentCol) + return; + if (value < 0) + _currentCol = 0; + else if (value > lines [_currentLine].Length) + _currentCol = lines [_currentLine].Length; + else + _currentCol = value; + NotifyValueChanged ("CurrentColumn", _currentCol); + } + } + [XmlAttributeAttribute][DefaultValue(0)] + public int CurrentLine{ + get { return _currentLine; } + set { + if (value == _currentLine) + return; + if (value >= lines.Count) + _currentLine = lines.Count-1; + else if (value < 0) + _currentLine = 0; + else + _currentLine = value; + //force recheck of currentCol for bounding + int cc = _currentCol; + _currentCol = 0; + CurrentColumn = cc; + NotifyValueChanged ("CurrentLine", _currentLine); + } + } + [XmlIgnore]public Point CurrentPosition { + get { return new Point(CurrentColumn, CurrentLine); } + } + //TODO:using HasFocus for drawing selection cause SelBegin and Release binding not to work + /// + /// Selection begin position in char units (line, column) + /// + [XmlAttributeAttribute][DefaultValue("-1")] + public Point SelBegin { + get { return _selBegin; } + set { + if (value == _selBegin) + return; + _selBegin = value; + System.Diagnostics.Debug.WriteLine ("SelBegin=" + _selBegin); + NotifyValueChanged ("SelBegin", _selBegin); + NotifyValueChanged ("SelectedText", SelectedText); + } + } + /// + /// Selection release position in char units (line, column) + /// + [XmlAttributeAttribute][DefaultValue("-1")] + public Point SelRelease { + get { + return _selRelease; + } + set { + if (value == _selRelease) + return; + _selRelease = value; + NotifyValueChanged ("SelRelease", _selRelease); + NotifyValueChanged ("SelectedText", SelectedText); + } + } + /// + /// return char at CurrentLine, CurrentColumn + /// + [XmlIgnore]protected Char CurrentChar + { + get { + return lines [CurrentLine] [CurrentColumn]; + } + } + /// + /// ordered selection start and end positions in char units + /// + [XmlIgnore]protected Point selectionStart + { + get { + return SelRelease < 0 || SelBegin.Y < SelRelease.Y ? SelBegin : + SelBegin.Y > SelRelease.Y ? SelRelease : + SelBegin.X < SelRelease.X ? SelBegin : SelRelease; + } + } + [XmlIgnore]public Point selectionEnd + { + get { + return SelRelease < 0 || SelBegin.Y > SelRelease.Y ? SelBegin : + SelBegin.Y < SelRelease.Y ? SelRelease : + SelBegin.X > SelRelease.X ? SelBegin : SelRelease; + } + } + [XmlIgnore]public string SelectedText + { + get { + + if (SelRelease < 0 || SelBegin < 0) + return ""; + if (selectionStart.Y == selectionEnd.Y) + return lines [selectionStart.Y].Substring (selectionStart.X, selectionEnd.X - selectionStart.X); + string tmp = ""; + tmp = lines [selectionStart.Y].Substring (selectionStart.X); + for (int l = selectionStart.Y + 1; l < selectionEnd.Y; l++) { + tmp += Interface.LineBreak + lines [l]; + } + tmp += Interface.LineBreak + lines [selectionEnd.Y].Substring (0, selectionEnd.X); + return tmp; + } + } + [XmlIgnore]public bool selectionIsEmpty + { get { return SelRelease == SelBegin; } } + + List getLines { + get { + return Regex.Split (_text, "\r\n|\r|\n|\\\\n").ToList(); + } + } + /// + /// Moves cursor one char to the left. + /// + /// true if move succeed + public bool MoveLeft(){ + int tmp = _currentCol - 1; + if (tmp < 0) { + if (_currentLine == 0) + return false; + CurrentLine--; + CurrentColumn = int.MaxValue; + } else + CurrentColumn = tmp; + return true; + } + /// + /// Moves cursor one char to the right. + /// + /// true if move succeed + public bool MoveRight(){ + int tmp = _currentCol + 1; + if (tmp > lines [_currentLine].Length){ + if (CurrentLine == lines.Count - 1) + return false; + CurrentLine++; + CurrentColumn = 0; + } else + CurrentColumn = tmp; + return true; + } + public void GotoWordStart(){ + if (lines[CurrentLine].Length == 0) + return; + CurrentColumn--; + //skip white spaces + while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0) + CurrentColumn--; + while (char.IsLetterOrDigit (lines [CurrentLine] [CurrentColumn]) && CurrentColumn > 0) + CurrentColumn--; + if (!char.IsLetterOrDigit (this.CurrentChar)) + CurrentColumn++; + } + public void GotoWordEnd(){ + //skip white spaces + if (CurrentColumn >= lines [CurrentLine].Length - 1) + return; + while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < lines [CurrentLine].Length-1) + CurrentColumn++; + while (char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < lines [CurrentLine].Length-1) + CurrentColumn++; + if (char.IsLetterOrDigit (this.CurrentChar)) + CurrentColumn++; + } + public void DeleteChar() + { + if (selectionIsEmpty) { + if (CurrentColumn == 0) { + if (CurrentLine == 0 && lines.Count == 1) + return; + CurrentLine--; + CurrentColumn = lines [CurrentLine].Length; + lines [CurrentLine] += lines [CurrentLine + 1]; + lines.RemoveAt (CurrentLine + 1); + OnTextChanged (this, null); + return; + } + CurrentColumn--; + lines [CurrentLine] = lines [CurrentLine].Remove (CurrentColumn, 1); + } else { + int linesToRemove = selectionEnd.Y - selectionStart.Y + 1; + int l = selectionStart.Y; + + if (linesToRemove > 0) { + lines [l] = lines [l].Remove (selectionStart.X, lines [l].Length - selectionStart.X) + + lines [selectionEnd.Y].Substring (selectionEnd.X, lines [selectionEnd.Y].Length - selectionEnd.X); + l++; + for (int c = 0; c < linesToRemove-1; c++) + lines.RemoveAt (l); + CurrentLine = selectionStart.Y; + CurrentColumn = selectionStart.X; + } else + lines [l] = lines [l].Remove (selectionStart.X, selectionEnd.X - selectionStart.X); + CurrentColumn = selectionStart.X; + SelBegin = -1; + SelRelease = -1; + } + OnTextChanged (this, null); + } + + #region GraphicObject overrides + public override Font Font { + get { return base.Font; } + set { + base.Font = value; + + using (ImageSurface img = new ImageSurface (Format.Argb32, 1, 1)) { + using (Context gr = new Context (img)) { + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + + fe = gr.FontExtents; + } + } + MaxScrollY = 0; + RegisterForGraphicUpdate (); + } + } + protected override int measureRawSize(LayoutingType lt) + { + if (lt == LayoutingType.Height) + return (int)Math.Ceiling(fe.Height * lines.Count) + Margin * 2; + + string txt = _text.Replace("\t", new String (' ', Interface.TabSize)); + + + int maxChar = 0; + foreach (string s in Regex.Split (txt, "\r\n|\r|\n|\\\\n")) { + if (maxChar < s.Length) + maxChar = s.Length; + } + return (int)(fe.MaxXAdvance * maxChar) + Margin * 2; + } + public override void OnLayoutChanges (LayoutingType layoutType) + { + base.OnLayoutChanges (layoutType); + + if (layoutType == LayoutingType.Height) + updateVisibleLines (); + } + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + gr.FontOptions = Interface.FontRenderingOptions; + gr.Antialias = Interface.Antialias; + + Rectangle cb = ClientRectangle; + + Foreground.SetAsSource (gr); + + bool selectionInProgress = false; + + #region draw text cursor + if (SelBegin != SelRelease) + selectionInProgress = true; + else if (HasFocus){ + gr.SetSourceColor(Color.Red); + gr.LineWidth = 2.0; + double cursorX = cb.X + (CurrentColumn - ScrollX) * fe.MaxXAdvance; + gr.MoveTo (0.5 + cursorX, cb.Y + (CurrentLine - ScrollY) * fe.Height); + gr.LineTo (0.5 + cursorX, cb.Y + (CurrentLine + 1 - ScrollY) * fe.Height); + gr.Stroke(); + } + #endregion + + Foreground.SetAsSource (gr); + + for (int i = 0; i < visibleLines; i++) { + int curL = i + ScrollY; + if (curL >= lines.Count) + break; + string lstr = lines[curL]; + + gr.MoveTo (cb.X, cb.Y + fe.Ascent + fe.Height * i); + gr.ShowText (lstr); + gr.Fill (); + + if (selectionInProgress && curL >= selectionStart.Y && curL <= selectionEnd.Y) { + + double rLineX = cb.X, + rLineY = cb.Y + i * fe.Height, + rLineW = lstr.Length * fe.MaxXAdvance; + + System.Diagnostics.Debug.WriteLine ("sel start: " + selectionStart + " sel end: " + selectionEnd); + if (curL == selectionStart.Y) { + rLineX += selectionStart.X * fe.MaxXAdvance; + rLineW -= selectionStart.X * fe.MaxXAdvance; + } + if (curL == selectionEnd.Y) + rLineW -= (lstr.Length - selectionEnd.X) * fe.MaxXAdvance; + + gr.Save (); + gr.Operator = Operator.Source; + gr.Rectangle (rLineX, rLineY, rLineW, fe.Height); + gr.SetSourceColor (SelectionBackground); + gr.FillPreserve (); + gr.Clip (); + gr.Operator = Operator.Over; + gr.SetSourceColor (SelectionForeground); + gr.MoveTo (cb.X, cb.Y + fe.Ascent + fe.Height * i); + gr.ShowText (lstr); + gr.Fill (); + gr.Restore (); + } + } + } + #endregion + + #region Mouse handling + void updatemouseLocalPos(Point mpos){ + mouseLocalPos = mpos - ScreenCoordinates(Slot).TopLeft - ClientRectangle.TopLeft; + if (mouseLocalPos.X < 0) + mouseLocalPos.X = 0; + if (mouseLocalPos.Y < 0) + mouseLocalPos.Y = 0; + + CurrentLine = ScrollY + (int)Math.Floor (mouseLocalPos.Y / fe.Height); + CurrentColumn = ScrollX + (int)Math.Round (mouseLocalPos.X / fe.MaxXAdvance); + } + public override void onMouseEnter (object sender, MouseMoveEventArgs e) + { + base.onMouseEnter (sender, e); + CurrentInterface.MouseCursor = XCursor.Text; + } + public override void onMouseLeave (object sender, MouseMoveEventArgs e) + { + base.onMouseLeave (sender, e); + CurrentInterface.MouseCursor = XCursor.Default; + } + protected override void onFocused (object sender, EventArgs e) + { + base.onFocused (sender, e); + +// SelBegin = new Point(0,0); +// SelRelease = new Point (lines.LastOrDefault ().Length, lines.Count-1); + RegisterForRedraw (); + } + protected override void onUnfocused (object sender, EventArgs e) + { + base.onUnfocused (sender, e); + +// SelBegin = -1; +// SelRelease = -1; + RegisterForRedraw (); + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + base.onMouseMove (sender, e); + + if (!e.Mouse.IsButtonDown (MouseButton.Left)) + return; + if (!HasFocus || SelBegin < 0) + return; + + updatemouseLocalPos (e.Position); + SelRelease = CurrentPosition; + + RegisterForRedraw(); + } + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + if (this.HasFocus){ + updatemouseLocalPos (e.Position); + SelBegin = SelRelease = CurrentPosition; + RegisterForRedraw();//TODO:should put it in properties + } + + //done at the end to set 'hasFocus' value after testing it + base.onMouseDown (sender, e); + } + public override void onMouseUp (object sender, MouseButtonEventArgs e) + { + base.onMouseUp (sender, e); + + if (SelBegin == SelRelease) + SelBegin = SelRelease = -1; + + updatemouseLocalPos (e.Position); + RegisterForRedraw (); + } + public override void onMouseDoubleClick (object sender, MouseButtonEventArgs e) + { + base.onMouseDoubleClick (sender, e); + + GotoWordStart (); + SelBegin = CurrentPosition; + GotoWordEnd (); + SelRelease = CurrentPosition; + RegisterForRedraw (); + } + #endregion + + #region Keyboard handling + public override void onKeyDown (object sender, KeyboardKeyEventArgs e) + { + base.onKeyDown (sender, e); + + Key key = e.Key; + + switch (key) + { + case Key.Back: + if (CurrentPosition == 0) + return; + this.DeleteChar(); + break; + case Key.Clear: + break; + case Key.Delete: + if (selectionIsEmpty) { + if (!MoveRight ()) + return; + }else if (e.Shift) + CurrentInterface.Clipboard = this.SelectedText; + this.DeleteChar (); + break; + case Key.Enter: + case Key.KeypadEnter: + if (!selectionIsEmpty) + this.DeleteChar (); + this.InsertLineBreak (); + break; + case Key.Escape: + Text = ""; + CurrentColumn = 0; + SelRelease = -1; + break; + case Key.Home: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = new Point (CurrentColumn, CurrentLine); + if (e.Control) + CurrentLine = 0; + CurrentColumn = 0; + SelRelease = new Point (CurrentColumn, CurrentLine); + break; + } + SelRelease = -1; + if (e.Control) + CurrentLine = 0; + CurrentColumn = 0; + break; + case Key.End: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + if (e.Control) + CurrentLine = int.MaxValue; + CurrentColumn = int.MaxValue; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + if (e.Control) + CurrentLine = int.MaxValue; + CurrentColumn = int.MaxValue; + break; + case Key.Insert: + if (e.Shift) + this.Insert (CurrentInterface.Clipboard); + else if (e.Control && !selectionIsEmpty) + CurrentInterface.Clipboard = this.SelectedText; + break; + case Key.Left: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = new Point(CurrentColumn, CurrentLine); + if (e.Control) + GotoWordStart (); + else if (!MoveLeft ()) + return; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + if (e.Control) + GotoWordStart (); + else + MoveLeft(); + break; + case Key.Right: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + if (e.Control) + GotoWordEnd (); + else if (!MoveRight ()) + return; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + if (e.Control) + GotoWordEnd (); + else + MoveRight (); + break; + case Key.Up: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + CurrentLine--; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + CurrentLine--; + break; + case Key.Down: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + CurrentLine++; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + CurrentLine++; + break; + case Key.Menu: + break; + case Key.NumLock: + break; + case Key.PageDown: + break; + case Key.PageUp: + break; + case Key.RWin: + break; + case Key.Tab: + this.Insert ("\t"); + break; + default: + break; + } + RegisterForGraphicUpdate(); + } + public override void onKeyPress (object sender, KeyPressEventArgs e) + { + base.onKeyPress (sender, e); + + this.Insert (e.KeyChar.ToString()); + + SelRelease = -1; + SelBegin = -1; //new Point(CurrentColumn, SelBegin.Y); + + RegisterForGraphicUpdate(); + } + #endregion + + + /// Compute x offset in cairo unit from text position + double GetXFromTextPointer(Context gr, Point pos) + { + try { + string l = lines [pos.Y].Substring (0, pos.X). + Replace ("\t", new String (' ', Interface.TabSize)); + return gr.TextExtents (l).XAdvance; + } catch{ + return -1; + } + } + + /// line break could be '\r' or '\n' or '\r\n' + string detectLineBreakKind(){ + string strLB = ""; + + if (string.IsNullOrEmpty(_text)) + return Interface.LineBreak; + int i = 0; + while ( i < _text.Length) { + if (_text [i] == '\r') { + strLB += '\r'; + i++; + } + if (i < _text.Length) { + if (_text [i] == '\r') + return "\r"; + if (_text [i] == '\n') + strLB += '\n'; + } + if (!string.IsNullOrEmpty (strLB)) + return strLB; + i++; + } + return Interface.LineBreak; + } + + void updateVisibleLines(){ + visibleLines = (int)Math.Floor ((double)ClientRectangle.Height / fe.Height); + MaxScrollY = Math.Max (0, lines.Count - visibleLines); + + System.Diagnostics.Debug.WriteLine ("update visible lines: " + visibleLines); + System.Diagnostics.Debug.WriteLine ("update MaxScrollY: " + MaxScrollY); + } + + + /// + /// Insert new string at caret position, should be sure no line break is inside. + /// + /// String. + protected void Insert(string str) + { + if (!selectionIsEmpty) + this.DeleteChar (); + string[] strLines = Regex.Split (str, "\r\n|\r|\n|" + @"\\n").ToArray(); + lines [CurrentLine] = lines [CurrentLine].Insert (CurrentColumn, strLines[0]); + CurrentColumn += strLines[0].Length; + for (int i = 1; i < strLines.Length; i++) { + InsertLineBreak (); + lines [CurrentLine] = lines [CurrentLine].Insert (CurrentColumn, strLines[i]); + CurrentColumn += strLines[i].Length; + } + OnTextChanged (this, null); + RegisterForGraphicUpdate(); + } + + /// + /// Insert a line break. + /// + protected void InsertLineBreak() + { + lines.Insert(CurrentLine + 1, lines[CurrentLine].Substring(CurrentColumn)); + lines [CurrentLine] = lines [CurrentLine].Substring (0, CurrentColumn); + CurrentLine++; + CurrentColumn = 0; + OnTextChanged (this, null); + } + } +} \ No newline at end of file diff --git a/src/TextBuffer.cs b/src/TextBuffer.cs new file mode 100644 index 0000000..f4afe8e --- /dev/null +++ b/src/TextBuffer.cs @@ -0,0 +1,32 @@ +// +// TextBuffer.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2017 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; + +namespace Crow +{ + public class TextBuffer + { + public TextBuffer () + { + } + } +} + diff --git a/ui/main.crow b/ui/main.crow index f4e4d44..84f1700 100755 --- a/ui/main.crow +++ b/ui/main.crow @@ -21,16 +21,17 @@ - - - - - + + +