]> O.S.I.I.S - jp/crowedit.git/commitdiff
basic scrolling text editor
authorJean-Philippe Bruyère <jp_bruyere@hotmail.com>
Sun, 2 Apr 2017 11:58:42 +0000 (13:58 +0200)
committerJean-Philippe Bruyère <jp_bruyere@hotmail.com>
Sun, 2 Apr 2017 11:58:42 +0000 (13:58 +0200)
14 files changed:
CrowEdit.cs [deleted file]
CrowEdit.csproj
OpenGL/Extensions.cs [new file with mode: 0644]
OpenGL/Shader.cs [new file with mode: 0644]
OpenGL/Texture.cs [new file with mode: 0644]
OpenGL/vaoMesh.cs [new file with mode: 0644]
src/CrowEdit.cs [new file with mode: 0644]
src/CrowEditExtentions.cs [new file with mode: 0644]
src/CrowWindow.cs [new file with mode: 0644]
src/InterfaceControler.cs [new file with mode: 0644]
src/ScrollingObject.cs [new file with mode: 0644]
src/ScrollingTextBox.cs [new file with mode: 0644]
src/TextBuffer.cs [new file with mode: 0644]
ui/main.crow

diff --git a/CrowEdit.cs b/CrowEdit.cs
deleted file mode 100644 (file)
index 1e0323f..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-//
-//  Main.cs
-//
-//  Author:
-//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
-//
-//  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 <http://www.gnu.org/licenses/>.
-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<string> undoStack = new List<string>();
-               List<string> redoStack = new List<string>();
-
-               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;
-                               }
-                       }
-               }
-
-       }
-}
-
index 4f9c898dea3917a6ca1b0cf71e7895b8dc65ee89..b34a2c4969d210d4a1fab81a32f3b47c640f741e 100644 (file)
     <Reference Include="System" />
     <Reference Include="System.Drawing" />
     <Reference Include="System.Xml" />
-    <Reference Include="Crow">
-      <HintPath>packages\Crow.OpenTK.0.5.1\lib\net45\Crow.dll</HintPath>
-    </Reference>
     <Reference Include="cairo-sharp">
       <Package>gtk-sharp-3.0</Package>
     </Reference>
+    <Reference Include="Crow">
+      <HintPath>packages\Crow.OpenTK.0.5.1\lib\net45\Crow.dll</HintPath>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="CrowEdit.cs" />
-    <Compile Include="CrowWindow.cs" />
-    <Compile Include="InterfaceControler.cs" />
     <Compile Include="OpenGL\Extensions.cs" />
     <Compile Include="OpenGL\Shader.cs" />
     <Compile Include="OpenGL\Texture.cs" />
     <Compile Include="OpenGL\vaoMesh.cs" />
+    <Compile Include="src\CrowEdit.cs" />
+    <Compile Include="src\CrowEditExtentions.cs" />
+    <Compile Include="src\CrowWindow.cs" />
+    <Compile Include="src\InterfaceControler.cs" />
+    <Compile Include="src\ScrollingObject.cs" />
+    <Compile Include="src\TextBuffer.cs" />
+    <Compile Include="src\ScrollingTextBox.cs" />
   </ItemGroup>
   <ItemGroup>
     <Folder Include="ui\" />
     <Folder Include="ui\icons\" />
+    <Folder Include="src\" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="ui\main.crow" />
diff --git a/OpenGL/Extensions.cs b/OpenGL/Extensions.cs
new file mode 100644 (file)
index 0000000..0dd1cca
--- /dev/null
@@ -0,0 +1,41 @@
+//
+//  Extensions.cs
+//
+//  Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+//  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 <http://www.gnu.org/licenses/>.
+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 (file)
index 0000000..6ae99b8
--- /dev/null
@@ -0,0 +1,336 @@
+//
+//  Shader.cs
+//
+//  Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+//  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 <http://www.gnu.org/licenses/>.
+
+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
+               /// <summary>
+               /// configure sources and compile
+               /// </summary>
+               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 (file)
index 0000000..ce24808
--- /dev/null
@@ -0,0 +1,94 @@
+//
+//  Texture.cs
+//
+//  Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+//  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 <http://www.gnu.org/licenses/>.
+
+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 (file)
index 0000000..19acd73
--- /dev/null
@@ -0,0 +1,221 @@
+//
+//  vaoMesh.cs
+//
+//  Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+//  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 <http://www.gnu.org/licenses/>.
+
+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<Vector3>(BufferTarget.ArrayBuffer,
+                               new IntPtr(positions.Length * Vector3.SizeInBytes),
+                               positions, BufferUsageHint.StaticDraw);
+
+                       if (texCoords != null) {
+                               texVboHandle = GL.GenBuffer ();
+                               GL.BindBuffer (BufferTarget.ArrayBuffer, texVboHandle);
+                               GL.BufferData<Vector2> (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 (file)
index 0000000..1e0323f
--- /dev/null
@@ -0,0 +1,228 @@
+//
+//  Main.cs
+//
+//  Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+//  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 <http://www.gnu.org/licenses/>.
+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<string> undoStack = new List<string>();
+               List<string> redoStack = new List<string>();
+
+               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 (file)
index 0000000..a2d7ac6
--- /dev/null
@@ -0,0 +1,41 @@
+//
+//  CrowEditExtentions.cs
+//
+//  Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+//  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 <http://www.gnu.org/licenses/>.
+using System;
+using System.Collections.Generic;
+
+namespace CrowEdit
+{
+       public static class CrowEditExtentions
+       {
+               public static List<int> AllIndexesOf(this string str, string value) {
+                       if (String.IsNullOrEmpty(value))
+                               throw new ArgumentException("the string to find may not be empty", "value");
+                       List<int> indexes = new List<int>();
+                       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 (file)
index 0000000..b1308d1
--- /dev/null
@@ -0,0 +1,370 @@
+//
+// CrowWindow.cs
+//
+// Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+// 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<ValueChangeEventArgs> 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<InterfaceControler> ifaceControl = new List<InterfaceControler>();
+               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<OpenTK.Input.MouseWheelEventArgs> MouseWheelChanged;
+               public event EventHandler<OpenTK.Input.MouseButtonEventArgs> MouseButtonUp;
+               public event EventHandler<OpenTK.Input.MouseButtonEventArgs> MouseButtonDown;
+               public event EventHandler<OpenTK.Input.MouseButtonEventArgs> MouseClick;
+               public event EventHandler<OpenTK.Input.MouseMoveEventArgs> MouseMove;
+               public event EventHandler<OpenTK.Input.KeyboardKeyEventArgs> KeyboardKeyDown;
+               public event EventHandler<OpenTK.Input.KeyboardKeyEventArgs> 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 ();
+               }
+               /// <summary>Override this method for your OpenGL rendering calls</summary>
+               public virtual void OnRender(FrameEventArgs e)
+               {
+               }
+               /// <summary>Override this method to customize clear method between frames</summary>
+               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<OpenTK.KeyPressEventArgs>(OpenTKGameWindow_KeyPress);
+                       Keyboard.KeyDown += new EventHandler<OpenTK.Input.KeyboardKeyEventArgs>(Keyboard_KeyDown);
+                       Keyboard.KeyUp += new EventHandler<OpenTK.Input.KeyboardKeyEventArgs>(Keyboard_KeyUp);
+                       Mouse.WheelChanged += new EventHandler<OpenTK.Input.MouseWheelEventArgs>(GL_Mouse_WheelChanged);
+                       Mouse.ButtonDown += new EventHandler<OpenTK.Input.MouseButtonEventArgs>(GL_Mouse_ButtonDown);
+                       Mouse.ButtonUp += new EventHandler<OpenTK.Input.MouseButtonEventArgs>(GL_Mouse_ButtonUp);
+                       Mouse.Move += new EventHandler<OpenTK.Input.MouseMoveEventArgs>(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 (file)
index 0000000..4d988c1
--- /dev/null
@@ -0,0 +1,267 @@
+//
+// InterfaceControler.cs
+//
+// Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+// 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<PerformanceMeasure> 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<PerformanceMeasure> (
+                               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 ();
+               }
+               /// <summary>Create the texture for the interface redering</summary>
+               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);
+               }
+               /// <summary>Rendering of the interface</summary>
+               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 (file)
index 0000000..f0463ae
--- /dev/null
@@ -0,0 +1,180 @@
+//
+// ScrollingObject.cs
+//
+// Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+// 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;
+
+               /// <summary> Horizontal Scrolling Position </summary>
+               [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 ();
+                       }
+               }
+               /// <summary> Vertical Scrolling Position </summary>
+               [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 ();
+                       }
+               }
+               /// <summary> Horizontal Scrolling maximum value </summary>
+               [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 ();
+                       }
+               }
+               /// <summary> Vertical Scrolling maximum value </summary>
+               [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 ();
+                       }
+               }
+               /// <summary> Mouse Wheel Scrolling multiplier </summary>
+               [XmlAttributeAttribute][DefaultValue(1)]
+               public virtual int MouseWheelSpeed {
+                       get { return mouseWheelSpeed; }
+                       set {
+                               if (mouseWheelSpeed == value)
+                                       return;
+                               
+                               mouseWheelSpeed = value;
+
+                               NotifyValueChanged ("MouseWheelSpeed", mouseWheelSpeed);
+                       }
+               }
+
+               /// <summary> Process scrolling vertically, or if shift is down, vertically </summary>
+               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;
+
+               }
+               /// <summary> Process scrolling with arrow keys, home and end keys. </summary>
+               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 (file)
index 0000000..a9b429a
--- /dev/null
@@ -0,0 +1,768 @@
+//
+// ScrollingTextBox.cs
+//
+// Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+// 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
+{
+       /// <summary>
+       /// Scrolling text box optimized for monospace fonts, for coding
+       /// </summary>
+       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<string> 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
+               /// <summary>
+               /// Selection begin position in char units (line, column)
+               /// </summary>
+               [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);
+                       }
+               }
+               /// <summary>
+               /// Selection release position in char units (line, column)
+               /// </summary>
+               [XmlAttributeAttribute][DefaultValue("-1")]
+               public Point SelRelease {
+                       get {
+                               return _selRelease;
+                       }
+                       set {
+                               if (value == _selRelease)
+                                       return;
+                               _selRelease = value;
+                               NotifyValueChanged ("SelRelease", _selRelease);
+                               NotifyValueChanged ("SelectedText", SelectedText);
+                       }
+               }
+               /// <summary>
+               /// return char at CurrentLine, CurrentColumn
+               /// </summary>
+               [XmlIgnore]protected Char CurrentChar
+               {
+                       get {
+                               return lines [CurrentLine] [CurrentColumn];
+                       }
+               }
+               /// <summary>
+               /// ordered selection start and end positions in char units
+               /// </summary>
+               [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<string> getLines {
+                       get {
+                               return Regex.Split (_text, "\r\n|\r|\n|\\\\n").ToList();
+                       }
+               }
+               /// <summary>
+               /// Moves cursor one char to the left.
+               /// </summary>
+               /// <returns><c>true</c> if move succeed</returns>
+               public bool MoveLeft(){
+                       int tmp = _currentCol - 1;
+                       if (tmp < 0) {
+                               if (_currentLine == 0)
+                                       return false;
+                               CurrentLine--;
+                               CurrentColumn = int.MaxValue;
+                       } else
+                               CurrentColumn = tmp;
+                       return true;
+               }
+               /// <summary>
+               /// Moves cursor one char to the right.
+               /// </summary>
+               /// <returns><c>true</c> if move succeed</returns>
+               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
+
+
+               /// <summary> Compute x offset in cairo unit from text position </summary>
+               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;
+                       }
+               }
+
+               /// <summary> line break could be '\r' or '\n' or '\r\n' </summary>
+               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);
+               }
+
+
+               /// <summary>
+               /// Insert new string at caret position, should be sure no line break is inside.
+               /// </summary>
+               /// <param name="str">String.</param>
+               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();
+               }
+
+               /// <summary>
+               /// Insert a line break.
+               /// </summary>
+               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 (file)
index 0000000..f4afe8e
--- /dev/null
@@ -0,0 +1,32 @@
+//
+//  TextBuffer.cs
+//
+//  Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+//  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 <http://www.gnu.org/licenses/>.
+using System;
+
+namespace Crow
+{
+       public class TextBuffer
+       {
+               public TextBuffer ()
+               {
+               }
+       }
+}
+
index f4e4d445c4179d2c3bf7c5928790c2d1101c9bfc..84f17006d2da9deab55db8909de783cd9198ab1f 100755 (executable)
                                <MenuItem Command="{CMDHelp}"/>
                        </MenuItem>
                </Menu>
-               <HorizontalStack Height="Stretched">
-                       <Scroller Background="Ivory" Name="scroller1" VerticalScrolling="true" ScrollY="{../scrollbar1.Value}">
-                               <TextBox Name="editor" Font="couriernew, 12" VerticalAlignment="Top" Margin="10"
-                                       Multiline="true" Height="Fit" TextAlignment="TopLeft"
-                                       Text="{²Text}"  KeyDown="textView_KeyDown"/>
-                       </Scroller>
-                       <ScrollBar Name="scrollbar1" Value="{../scroller1.ScrollY}" 
-                               Maximum="{../scroller1.MaximumScroll}" Orientation="Vertical" 
+               <HorizontalStack Height="Stretched" Background="Ivory">
+                       <ScrollingTextBox Focusable="true" Name="editor" Font="couriernew, 16" VerticalAlignment="Top" Margin="10"
+                                       Foreground="Jet"
+                                       Text="{Text}"  KeyDown="textView_KeyDown"/>
+                       <ScrollBar Name="scrollbarY" Value="{²../editor.ScrollY}" 
+                               Maximum="{../editor.MaxScrollY}" Orientation="Vertical" 
                                Width="14" />
                </HorizontalStack>
+               <ScrollBar Name="scrollbarX" Value="{²../editor.ScrollX}" 
+                               Maximum="{../editor.MaxScrollX}" Orientation="Horizontal" 
+                               Height="14" />
                <HorizontalStack Height="Fit">
                        <GraphicObject Height="5" Width="Stretched"/>
                        <GraphicObject Background="Red" Width="10" Height="10" Visible="{IsDirty}"/>