// // Interface.cs // // Author: // Jean-Philippe Bruyère // // Copyright (c) 2013-2017 Jean-Philippe Bruyère // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Xml; using System.Xml.Serialization; using Cairo; using System.Globalization; using Crow.Native; namespace Crow { /// /// The Interface Class is the top container of the application. /// It provides the Dirty bitmap and zone of the interface to be drawn on screen /// /// The Interface contains : /// - rendering and layouting queues and logic. /// - helpers to load XML interfaces files /// - global constants and variables of CROW /// - Keyboard and Mouse logic /// - the resulting bitmap of the interface /// public class Interface : Group { #region CTOR static Interface(){ loadCursors (); loadStyling (); findAvailableTemplates (); FontRenderingOptions = new FontOptions (); FontRenderingOptions.Antialias = Antialias.Subpixel; FontRenderingOptions.HintMetrics = HintMetrics.On; FontRenderingOptions.HintStyle = HintStyle.Medium; FontRenderingOptions.SubpixelOrder = SubpixelOrder.Rgb; } public Interface() : base (){ CacheEnabled = true; CultureInfo.DefaultThreadCurrentCulture = System.Globalization.CultureInfo.InvariantCulture; } protected override void initLibcrow () { CurrentInterface = this; layoutingCtx = LibCrow.crow_context_create (); base.initLibcrow (); unsafe { LibCrow.crow_context_set_root (layoutingCtx, nativeHnd); //nativeHnd->Background = Cairo.NativeMethods.cairo_pattern_create_rgb (1, 0, 1); } } #endregion #region Static and constants /// If true, mouse focus is given when mouse is over control public static bool FocusOnHover = false; /// Threshold to catch borders for sizing public static int BorderThreshold = 5; /// Double click threshold in milisecond public static int DoubleClick = 200;//max duration between two mouse_down evt for a dbl clk in milisec. /// Time to wait in millisecond before starting repeat loop public static int DeviceRepeatDelay = 700; /// Time interval in millisecond between device event repeat public static int DeviceRepeatInterval = 40; /// Tabulation size in Text controls public static int TabSize = 4; public static string LineBreak = "\r\n"; /// Allow rendering of interface in development environment public static bool DesignerMode = false; /// Disable caching for a widget if this threshold is reached public const int MaxCacheSize = 2048; /// Above this count, the layouting is discard from the current /// update cycle and requeued for the next public const int MaxLayoutingTries = 3; /// Above this count, the layouting is discard for the widget and it /// will not be rendered on screen public const int MaxDiscardCount = 5; /// Global font rendering settings for Cairo public static FontOptions FontRenderingOptions; /// Global font rendering settings for Cairo public static Antialias Antialias = Antialias.Subpixel; /// /// Each control need a ref to the root interface containing it, if not set in GraphicObject.currentInterface, /// the ref of this one will be stored in GraphicObject.currentInterface /// internal static Interface CurrentInterface; internal Stopwatch clickTimer = new Stopwatch(); internal GraphicObject eligibleForDoubleClick = null; internal IntPtr layoutingCtx = IntPtr.Zero; #endregion #region Events public event EventHandler MouseCursorChanged; public event EventHandler Quit; public event EventHandler MouseWheelChanged; public event EventHandler MouseButtonUp; public event EventHandler MouseButtonDown; public event EventHandler MouseClick; public event EventHandler MouseMove; public event EventHandler KeyboardKeyDown; public event EventHandler KeyboardKeyUp; #endregion #region Public Fields /// Graphic Tree of this interface public GraphicObject[] GraphicTree = new GraphicObject[1024]; public int GraphiTreePointer = 0; /// Interface's resulting bitmap public byte[] bmp; /// resulting bitmap limited to last redrawn part public byte[] dirtyBmp; /// True when host has to repaint Interface public bool IsDirty = false; /// Coordinate of the dirty bmp on the original bmp public Rectangle DirtyRect; /// Locked for each layouting operation public object LayoutMutex = new object(); /// Sync mutex between host and Crow for rendering operations (bmp, dirtyBmp,...) public object RenderMutex = new object(); /// Global lock of the update cycle public object UpdateMutex = new object(); //TODO:share resource instances /// /// Store loaded resources instances shared among controls to reduce memory footprint /// public Dictionary Ressources = new Dictionary(); /// Main drawing queue, holding layouted controls public Queue DrawingQueue = new Queue(); public string Clipboard;//TODO:use object instead for complex copy paste /// each IML and fragments (such as inline Templates) are compiled as a Dynamic Method stored here /// on the first instance creation of a IML item. /// public static Dictionary Instantiators = new Dictionary(); public List CrowThreads = new List();//used to monitor thread finished #endregion #region Private Fields /// Client rectangle in the host context Rectangle clientRectangle; /// Clipping rectangles on the root context Region clipping = new Region(); /// Main Cairo context Context ctx; /// Main Cairo surface Surface surf; #endregion #region Default values and Style loading /// Default values of properties from GraphicObjects are retrieve from XML Attributes. /// The reflexion process used to retrieve those values being very slow, it is compiled in MSIL /// and injected as a dynamic method referenced in the DefaultValuesLoader Dictionnary. /// The compilation is done on the first object instancing, and is also done for custom widgets public delegate void LoaderInvoker(object instance); /// Store one loader per StyleKey public static Dictionary DefaultValuesLoader = new Dictionary(); /// Store dictionnary of member/value per StyleKey public static Dictionary Styling; /// parse all styling data's during application startup and build global Styling Dictionary static void loadStyling() { Styling = new Dictionary (); //fetch styling info in this order, if member styling is alreadey referenced in previous //assembly, it's ignored. loadStylingFromAssembly (Assembly.GetEntryAssembly ()); loadStylingFromAssembly (Assembly.GetExecutingAssembly ()); } /// Search for .style resources in assembly static void loadStylingFromAssembly (Assembly assembly) { foreach (string s in assembly .GetManifestResourceNames () .Where (r => r.EndsWith (".style", StringComparison.OrdinalIgnoreCase))) { new StyleReader (assembly, s) .Dispose (); } } static void loadCursors(){ //Load cursors XCursor.Cross = XCursorFile.Load("#Crow.Images.Icons.Cursors.cross").Cursors[0]; XCursor.Default = XCursorFile.Load("#Crow.Images.Icons.Cursors.arrow").Cursors[0]; XCursor.NW = XCursorFile.Load("#Crow.Images.Icons.Cursors.top_left_corner").Cursors[0]; XCursor.NE = XCursorFile.Load("#Crow.Images.Icons.Cursors.top_right_corner").Cursors[0]; XCursor.SW = XCursorFile.Load("#Crow.Images.Icons.Cursors.bottom_left_corner").Cursors[0]; XCursor.SE = XCursorFile.Load("#Crow.Images.Icons.Cursors.bottom_right_corner").Cursors[0]; XCursor.H = XCursorFile.Load("#Crow.Images.Icons.Cursors.sb_h_double_arrow").Cursors[0]; XCursor.V = XCursorFile.Load("#Crow.Images.Icons.Cursors.sb_v_double_arrow").Cursors[0]; XCursor.Text = XCursorFile.Load("#Crow.Images.Icons.Cursors.ibeam").Cursors[0]; } #endregion #region Templates /// Store one default templates resource ID per class. /// Resource ID must be 'fullClassName.template' (not case sensitive) /// Those found in application assembly have priority to the default Crow's one /// public static Dictionary DefaultTemplates = new Dictionary(); /// Finds available default templates at startup static void findAvailableTemplates(){ searchTemplatesIn (Assembly.GetEntryAssembly ()); searchTemplatesIn (Assembly.GetExecutingAssembly ()); } static void searchTemplatesIn(Assembly assembly){ foreach (string resId in assembly .GetManifestResourceNames () .Where (r => r.EndsWith (".template", StringComparison.OrdinalIgnoreCase))) { string clsName = resId.Substring (0, resId.Length - 9); if (DefaultTemplates.ContainsKey (clsName)) continue; DefaultTemplates[clsName] = "#" + resId; } } #endregion #region Load/Save /// Open file or find a resource from path string /// A file or resource stream /// This could be a normal file path, or an embedded ressource ID /// Resource ID's must be prefixed with '#' character public static Stream GetStreamFromPath (string path) { Stream stream = null; if (path.StartsWith ("#")) { string resId = path.Substring (1); //try/catch added to prevent nunit error try { stream = System.Reflection.Assembly.GetEntryAssembly ().GetManifestResourceStream (resId); } catch{} if (stream == null)//try to find ressource in Crow assembly stream = System.Reflection.Assembly.GetExecutingAssembly ().GetManifestResourceStream (resId); if (stream == null) throw new Exception ("Resource not found: " + path); } else { if (!File.Exists (path)) throw new FileNotFoundException ("File not found: ", path); stream = new FileStream (path, FileMode.Open, FileAccess.Read); } return stream; } /// Create an instance of a GraphicObject and add it to the GraphicTree /// of this Interface public GraphicObject LoadInterface (string path) { lock (UpdateMutex) { GraphicObject tmp = Load (path); AddChild (tmp); return tmp; } } /// Create an instance of a GraphicObject linked to this interface but /// not added to the GraphicTree public GraphicObject Load (string path) { //try { return GetInstantiator (path).CreateInstance (this); //} catch (Exception ex) { // throw new Exception ("Error loading <" + path + ">:", ex); //} } /// Fetch it from cache or create it public static Instantiator GetInstantiator(string path){ if (!Instantiators.ContainsKey(path)) Instantiators [path] = new Instantiator(path); return Instantiators [path]; } /// Item templates have additional properties for recursivity and /// custom display per item type public static ItemTemplate GetItemTemplate(string path){ if (!Instantiators.ContainsKey(path)) Instantiators [path] = new ItemTemplate(path); return Instantiators [path] as ItemTemplate; } //TODO: .Net xml serialisation is no longer used, it has been replaced with instantiators public static void Save (string file, T graphicObject) { XmlSerializerNamespaces xn = new XmlSerializerNamespaces (); xn.Add ("", ""); XmlSerializer xs = new XmlSerializer (typeof(T)); xs = new XmlSerializer (typeof(T)); using (Stream s = new FileStream (file, FileMode.Create)) { xs.Serialize (s, graphicObject, xn); } } #endregion #region focus GraphicObject _activeWidget; //button is pressed on widget GraphicObject _hoverWidget; //mouse is over GraphicObject _focusedWidget; //has keyboard (or other perif) focus /// Widget is focused and button is down or another perif action is occuring /// , it can not lose focus while Active public GraphicObject ActiveWidget { get { return _activeWidget; } set { if (_activeWidget == value) return; if (_activeWidget != null) _activeWidget.IsActive = false; _activeWidget = value; if (_activeWidget != null) { _activeWidget.IsActive = true; #if DEBUG_FOCUS Debug.WriteLine("Active => " + _activeWidget.ToString()); }else Debug.WriteLine("Active => null"); #else } #endif } } /// Pointer is over the widget public GraphicObject HoverWidget { get { return _hoverWidget; } set { if (_hoverWidget == value) return; _hoverWidget = value; #if DEBUG_FOCUS if (_hoverWidget != null) Debug.WriteLine("Hover => " + _hoverWidget.ToString()); else Debug.WriteLine("Hover => null"); #endif } } /// Widget has the keyboard or mouse focus public GraphicObject FocusedWidget { get { return _focusedWidget; } set { if (_focusedWidget == value) return; if (_focusedWidget != null) _focusedWidget.HasFocus = false; _focusedWidget = value; if (_focusedWidget != null) _focusedWidget.HasFocus = true; } } #endregion public override void AddChild (GraphicObject g) { lock(RenderMutex) base.AddChild (g); } public override void RemoveChild (GraphicObject child) { lock(RenderMutex) base.RemoveChild (child); } #region UPDATE Loops /// Enqueue Graphic object for Repaint, DrawingQueue is locked because /// GraphObj's property Set methods could trigger an update from another thread public void EnqueueForRepaint(GraphicObject g) { lock (DrawingQueue) { if (g.IsQueueForRedraw) return; DrawingQueue.Enqueue (g); g.IsQueueForRedraw = true; } } /// Main Update loop, executed in this interface thread, lock the UpdateMutex /// Steps: /// - execute device Repeat events /// - Layouting /// - Clipping /// - Drawing /// Result: the Interface bitmap is drawn in memory (byte[] bmp) and a dirtyRect and bitmap are available /// public void Update(){ if (mouseRepeatCount > 0) { int mc = mouseRepeatCount; mouseRepeatCount -= mc; if (_focusedWidget != null) { for (int i = 0; i < mc; i++) { _focusedWidget.onMouseClick (this, new MouseButtonEventArgs (Mouse.X, Mouse.Y, MouseButton.Left, true)); } } } if (keyboardRepeatCount > 0) { int mc = keyboardRepeatCount; keyboardRepeatCount -= mc; if (_focusedWidget != null) { for (int i = 0; i < mc; i++) { _focusedWidget.onKeyDown (this, lastKeyDownEvt); } } } CrowThread[] tmpThreads; lock (CrowThreads) { tmpThreads = new CrowThread[CrowThreads.Count]; Array.Copy (CrowThreads.ToArray (), tmpThreads, CrowThreads.Count); } for (int i = 0; i < tmpThreads.Length; i++) tmpThreads [i].CheckState (); if (!Monitor.TryEnter (UpdateMutex)) return; #if MEASURE_TIME updateMeasure.StartCycle(); #endif processLayouting (); #if DEBUG_LAYOUTING if (curLQIsTries.Count > 0){ LQIsTries.Add(curLQIsTries); curLQIsTries = new LQIList(); LQIs.Add(curLQIs); curLQIs = new LQIList(); } #endif clippingRegistration (); processDrawing (); #if MEASURE_TIME updateMeasure.StopCycle(); #endif Monitor.Exit (UpdateMutex); } /// Layouting loop, this is the first step of the udpate and process registered /// Layouting queue items. Failing LQI's are requeued in this cycle until MaxTry is reached which /// trigger an enqueue for the next Update Cycle void processLayouting(){ #if MEASURE_TIME layoutingMeasure.StartCycle(); #endif if (Monitor.TryEnter (LayoutMutex)) { LibCrow.crow_context_process_layouting (layoutingCtx); Monitor.Exit (LayoutMutex); } #if MEASURE_TIME layoutingMeasure.StopCycle(); #endif } /// Degueue Widget to clip from DrawingQueue and register the last painted slot and the new one /// Clipping rectangles are added at each level of the tree from leef to root, that's the way for the painting /// operation to known if it should go down in the tree for further graphic updates and repaints void clippingRegistration(){ #if MEASURE_TIME clippingMeasure.StartCycle(); #endif LibCrow.crow_context_process_clipping (layoutingCtx); #if MEASURE_TIME clippingMeasure.StopCycle(); #endif } /// Clipping Rectangles drive the drawing process. For compositing, each object under a clip rectangle should be /// repainted. If it contains also clip rectangles, its cache will be update, or if not cached a full redraw will take place unsafe void processDrawing(){ #if MEASURE_TIME drawingMeasure.StartCycle(); #endif using (surf = new ImageSurface (bmp, Format.Argb32, ClientRectangle.Width, ClientRectangle.Height, ClientRectangle.Width * 4)) { using (ctx = new Context (surf)) { LibCrow.crow_context_process_drawing (layoutingCtx, ctx.Handle); } } #if MEASURE_TIME drawingMeasure.StopCycle(); #endif } #endregion public void ProcessResize(Rectangle bounds){ lock (UpdateMutex) { clientRectangle = bounds; int stride = 4 * ClientRectangle.Width; int bmpSize = Math.Abs (stride) * ClientRectangle.Height; bmp = new byte[bmpSize]; dirtyBmp = new byte[bmpSize]; LibCrow.crow_context_resize (layoutingCtx, bounds.Width, bounds.Height); } } #region Mouse and Keyboard Handling XCursor cursor = XCursor.Default; public MouseState Mouse; public KeyboardState Keyboard; public XCursor MouseCursor { set { if (value == cursor) return; cursor = value; MouseCursorChanged.Raise (this,new MouseCursorChangedEventArgs(cursor)); } } /// Processes mouse move events from the root container /// trueif mouse is in the interface public bool ProcessMouseMove(int x, int y) { int deltaX = x - Mouse.X; int deltaY = y - Mouse.Y; Mouse.X = x; Mouse.Y = y; MouseMoveEventArgs e = new MouseMoveEventArgs (x, y, deltaX, deltaY); e.Mouse = Mouse; if (ActiveWidget != null) { //TODO, ensure object is still in the graphic tree //send move evt even if mouse move outside bounds ActiveWidget.onMouseMove (this, e); return true; } // if (HoverWidget != null) { // //check topmost graphicobject first // GraphicObject tmp = HoverWidget; // GraphicObject topc = null; // while (!(tmp is Interface)) { // topc = tmp; // tmp = tmp.LogicalParent; // } // int idxhw = GraphicTree.IndexOf (topc); // if (idxhw != 0) { // int i = 0; // while (i < idxhw) { // if (GraphicTree [i].localLogicalParentIsNull) { // if (GraphicTree [i].MouseIsIn (e.Position)) { // while (HoverWidget != null) { // HoverWidget.onMouseLeave (HoverWidget, e); // HoverWidget = HoverWidget.LogicalParent; // } // // GraphicTree [i].checkHoverWidget (e); // return true; // } // } // i++; // } // } // // if (HoverWidget.MouseIsIn (e.Position)) { // HoverWidget.checkHoverWidget (e); // return true; // } else { // HoverWidget.onMouseLeave (HoverWidget, e); // //seek upward from last focused graph obj's // while (!(HoverWidget.LogicalParent is Interface)) { // HoverWidget = HoverWidget.LogicalParent; // if (HoverWidget.MouseIsIn (e.Position)) { // HoverWidget.checkHoverWidget (e); // return true; // } else // HoverWidget.onMouseLeave (HoverWidget, e); // } // } // } // // //top level graphic obj's parsing // lock (GraphicTree) { // for (int i = 0; i < GraphicTree.Count; i++) { // GraphicObject g = GraphicTree [i]; // if (g.MouseIsIn (e.Position)) { // g.checkHoverWidget (e); //// if (g is Window) //// PutOnTop (g); // return true; // } // } // } HoverWidget = null; return false; } public bool ProcessMouseButtonUp(int button) { Mouse.DisableBit (button); MouseButtonEventArgs e = new MouseButtonEventArgs () { Mouse = Mouse }; if (_activeWidget == null) return false; if (mouseRepeatThread != null) { mouseRepeatOn = false; mouseRepeatThread.Abort(); mouseRepeatThread.Join (); } _activeWidget.onMouseUp (_activeWidget, e); ActiveWidget = null; return true; } public bool ProcessMouseButtonDown(int button) { Mouse.EnableBit (button); MouseButtonEventArgs e = new MouseButtonEventArgs () { Mouse = Mouse }; if (HoverWidget == null) return false; HoverWidget.onMouseDown(HoverWidget,new BubblingMouseButtonEventArg(e)); if (FocusedWidget == null) return true; if (!FocusedWidget.MouseRepeat) return true; mouseRepeatThread = new Thread (mouseRepeatThreadFunc); mouseRepeatThread.IsBackground = true; mouseRepeatThread.Start (); return true; } public bool ProcessMouseWheelChanged(float delta) { Mouse.SetScrollRelative (0, delta); MouseWheelEventArgs e = new MouseWheelEventArgs () { Mouse = Mouse, DeltaPrecise = delta }; if (HoverWidget == null) return false; HoverWidget.onMouseWheel (this, e); return true; } public bool ProcessKeyDown(int Key){ Keyboard.SetKeyState((Crow.Key)Key,true); KeyboardKeyEventArgs e = lastKeyDownEvt = new KeyboardKeyEventArgs((Crow.Key)Key, false, Keyboard); lastKeyDownEvt.IsRepeat = true; KeyboardKeyDown.Raise (this, e); keyboardRepeatThread = new Thread (keyboardRepeatThreadFunc); keyboardRepeatThread.IsBackground = true; keyboardRepeatThread.Start (); if (_focusedWidget == null) return false; _focusedWidget.onKeyDown (this, e); return true; } public bool ProcessKeyUp(int Key){ Keyboard.SetKeyState ((Crow.Key)Key, false); KeyboardKeyEventArgs e = new KeyboardKeyEventArgs((Crow.Key)Key, false, Keyboard); KeyboardKeyUp (this, e); if (keyboardRepeatThread != null) { keyboardRepeatOn = false; keyboardRepeatThread.Abort(); keyboardRepeatThread.Join (); } if (_focusedWidget == null) return false; _focusedWidget.onKeyUp (this, e); return true; } public bool ProcessKeyPress(char Key){ if (_focusedWidget == null) return false; KeyPressEventArgs e = new KeyPressEventArgs(Key); _focusedWidget.onKeyPress (this, e); return true; } public override void onMouseMove (object sender, MouseMoveEventArgs e) {} public override void onMouseUp (object sender, MouseButtonEventArgs e) {} public override void onMouseDown (object sender, MouseButtonEventArgs e) {} public override void onMouseClick (object sender, MouseButtonEventArgs e) {} public override void onMouseDoubleClick (object sender, MouseButtonEventArgs e) {} public override void onMouseWheel (object sender, MouseWheelEventArgs e) {} #endregion #region Device Repeat Events volatile bool mouseRepeatOn, keyboardRepeatOn; volatile int mouseRepeatCount, keyboardRepeatCount; Thread mouseRepeatThread, keyboardRepeatThread; KeyboardKeyEventArgs lastKeyDownEvt; void mouseRepeatThreadFunc() { mouseRepeatOn = true; Thread.Sleep (Interface.DeviceRepeatDelay); while (mouseRepeatOn) { mouseRepeatCount++; Thread.Sleep (Interface.DeviceRepeatInterval); } mouseRepeatCount = 0; } void keyboardRepeatThreadFunc() { keyboardRepeatOn = true; Thread.Sleep (Interface.DeviceRepeatDelay); while (keyboardRepeatOn) { keyboardRepeatCount++; Thread.Sleep (Interface.DeviceRepeatInterval); } keyboardRepeatCount = 0; } #endregion public override void RegisterClip(Rectangle r){ clipping.UnionRectangle (r); } public override void RegisterForLayouting (LayoutingType layoutType) { } public override bool UpdateLayout (LayoutingType layoutType) { throw new NotImplementedException (); } public override Rectangle ContextCoordinates (Rectangle r) { return r;} public override Rectangle ScreenCoordinates (Rectangle r) { return r; } public override GraphicObject Parent { get { return null; } set { throw new NotImplementedException (); } } public override Rectangle ClientRectangle { get { return clientRectangle; } } public override Rectangle getSlot () { return ClientRectangle; } public override Measure WidthPolicy { get { return Measure.Stretched; }} public override Measure HeightPolicy { get { return Measure.Stretched; }} public override Measure Width { set { throw new NotImplementedException (); } get { return clientRectangle.Width; } } public override Measure Height { set { throw new NotImplementedException (); } get { return clientRectangle.Height; } } public override bool MouseIsIn (Point m) => true; #if DEBUG public void DumpTo (string path) { using (surf = new ImageSurface (bmp, Format.Argb32, ClientRectangle.Width, ClientRectangle.Height, ClientRectangle.Width * 4)) { surf.WriteToPng (path); } } #endif #if MEASURE_TIME public PerformanceMeasure clippingMeasure = new PerformanceMeasure("Clipping", 100); public PerformanceMeasure layoutingMeasure = new PerformanceMeasure("Layouting", 100); public PerformanceMeasure updateMeasure = new PerformanceMeasure("Update", 100); public PerformanceMeasure drawingMeasure = new PerformanceMeasure("Drawing", 100); #endif #if DEBUG_LAYOUTING public List LQIsTries = new List(); public LQIList curLQIsTries = new LQIList(); public List LQIs = new List(); public LQIList curLQIs = new LQIList(); // public static LayoutingQueueItem[] MultipleRunsLQIs { // get { return curUpdateLQIs.Where(l=>l.LayoutingTries>2 || l.DiscardCount > 0).ToArray(); } // } public LayoutingQueueItem currentLQI; #else //public List LQIs = null;//still create the var for CrowIDE #endif bool disposed = false; protected override void Dispose (bool disposing) { if (disposed) return; if (disposing) { clipping.Dispose (); } LibCrow.crow_context_destroy (layoutingCtx); disposed = true; base.Dispose (disposing); } } }