+++ /dev/null
-//
-// 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;
- }
- }
- }
-
- }
-}
-
<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" />
--- /dev/null
+//
+// 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;
+ }
+
+ }
+}
+
--- /dev/null
+//
+// 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
+ }
+}
+
--- /dev/null
+//
+// 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;
+ }
+ }
+
+}
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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;
+ }
+ }
+ }
+
+ }
+}
+
--- /dev/null
+//
+// 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);
+ }
+ }
+ }
+}
+
--- /dev/null
+//
+// 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
+ }
+}
--- /dev/null
+//
+// 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
+ }
+}
+
--- /dev/null
+//
+// 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;
+ }
+ }
+ }
+}
+
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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 ()
+ {
+ }
+ }
+}
+
<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}"/>