diff options
| author | j-p <jp.bruyere@live.be> | 2016-03-06 08:10:40 +0000 |
|---|---|---|
| committer | j-p <jp.bruyere@live.be> | 2016-03-06 08:10:40 +0000 |
| commit | a14493ce86f811fa37aae035bad9cf189274176b (patch) | |
| tree | 451ebbe361268e0c730f2f4013150ace378efbd9 /src/Interface.cs | |
| parent | c7a9d36bd679f827b649e2b792465b127fd577d1 (diff) | |
| parent | ed16031271322617fd9898ac27df5d91caabe907 (diff) | |
Merge pull request #4 from jpbruyere/MultithreadedInterface
Multithreaded interface
Diffstat (limited to 'src/Interface.cs')
| -rw-r--r-- | src/Interface.cs | 569 |
1 files changed, 551 insertions, 18 deletions
diff --git a/src/Interface.cs b/src/Interface.cs index 6b2e82fe..3f4cc918 100644 --- a/src/Interface.cs +++ b/src/Interface.cs @@ -30,11 +30,31 @@ using System.Reflection.Emit; using System.CodeDom; using Microsoft.CSharp; using System.CodeDom.Compiler; +using System.Threading; +using Cairo; namespace Crow { - public static class Interface + public class Interface : ILayoutable { + public delegate void ResizeDelegatePrototype(Rectangle bounds); + public delegate void LoaderDelegatePrototype (string path); + public ResizeDelegatePrototype ResizeDelegate; + public LoaderDelegatePrototype LoadInterfaceDelegate; + + #region CTOR + static Interface(){ + Interface.LoadCursors (); + } + public Interface(){ + Interface.CurrentInterface = this; + + LoadInterfaceDelegate = new LoaderDelegatePrototype(InterfaceLoad); + ResizeDelegate = new ResizeDelegatePrototype (ProcessResize); + } + #endregion + + #region Static and constants /// <summary> Used to prevent spurious loading of templates </summary> internal static bool XmlSerializerInit = false; /// <summary> keep ressource path for debug msg </summary> @@ -55,25 +75,50 @@ namespace Crow public static int BorderThreshold = 5; public const int MaxCacheSize = 2048; public const int MaxLayoutingTries = 50; + #endregion - public static Queue<LayoutingQueueItem> LayoutingQueue = new Queue<LayoutingQueueItem>(); - public delegate void loadDefaultInvoker(object instance); - public static Dictionary<String, loadDefaultInvoker> DefaultValuesLoader = new Dictionary<string, loadDefaultInvoker>(); + public Queue<LayoutingQueueItem> LayoutingQueue = new Queue<LayoutingQueueItem>(); + public Queue<GraphicObject> GraphicUpdateQueue = new Queue<GraphicObject>(); - #region Load/Save + public static void RegisterForGraphicUpdate(GraphicObject g) + { + lock (CurrentInterface.GraphicUpdateQueue) { + if (g.IsQueueForGraphicUpdate) + return; + if (CurrentInterface == null) + return; + CurrentInterface.GraphicUpdateQueue.Enqueue (g); + g.IsQueueForGraphicUpdate = true; + } + } + public static void AddToRedrawList(GraphicObject g) + { + if (g.IsInRedrawList) + return; + if (Interface.CurrentInterface == null) + return; + Interface.CurrentInterface.RedrawList.Add (g); + g.IsInRedrawList = true; + } - public static void Save<T> (string file, T graphicObject) - { - XmlSerializerNamespaces xn = new XmlSerializerNamespaces (); - xn.Add ("", ""); - XmlSerializer xs = new XmlSerializer (typeof(T)); + #region default values loading helpers + public delegate void loadDefaultInvoker(object instance); + public static Dictionary<String, loadDefaultInvoker> DefaultValuesLoader = new Dictionary<string, loadDefaultInvoker>(); + #endregion - xs = new XmlSerializer (typeof(T)); - using (Stream s = new FileStream (file, FileMode.Create)) { - xs.Serialize (s, graphicObject, xn); - } + public 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]; } + #region Load/Save public static Stream GetStreamFromPath (string path) { Stream stream = null; @@ -84,7 +129,7 @@ namespace Crow try { stream = System.Reflection.Assembly.GetEntryAssembly ().GetManifestResourceStream (resId); } catch{} - if (stream == null)//try to find ressource in Crow assembly + 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); @@ -120,7 +165,17 @@ namespace Crow return t; } + public static void Save<T> (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); + } + } public static GraphicObject Load (string path, object hostClass = null) { Interface.XmlLoaderCount ++; @@ -132,9 +187,6 @@ namespace Crow Interface.XmlLoaderCount --; return tmp; } - - - public static GraphicObject Load (Stream stream, Type type, object hostClass = null) { #if DEBUG_LOAD @@ -169,7 +221,488 @@ namespace Crow return result; } + public void InterfaceLoad(string path){ + GraphicObject tmp = Interface.Load (path, this); + AddWidget (tmp); + } + + public GraphicObject LoadInterface (string path) + { + lock (UpdateMutex) { + GraphicObject tmp = Interface.Load (path, this); + AddWidget (tmp); + return tmp; + } + } + + #endregion + + #if MEASURE_TIME + public Stopwatch clippingTime = new Stopwatch (); + public Stopwatch layoutTime = new Stopwatch (); + public Stopwatch guTime = new Stopwatch (); + public Stopwatch drawingTime = new Stopwatch (); + #endif + + public List<GraphicObject> GraphicObjects = new List<GraphicObject>(); + public Color Background = Color.Transparent; + + internal static Interface currentWindow; + public static Interface CurrentInterface; + + Rectangles _redrawClip = new Rectangles();//should find another way to access it from child + List<GraphicObject> _gobjsToRedraw = new List<GraphicObject>(); + + Context ctx; + Surface surf; + public byte[] bmp; + public byte[] dirtyBmp; + public bool IsDirty = false; + public Rectangle DirtyRect; + public object LayoutMutex = new object(); + public object RenderMutex = new object(); + public object UpdateMutex = new object(); + + #region focus + GraphicObject _activeWidget; //button is pressed on widget + GraphicObject _hoverWidget; //mouse is over + GraphicObject _focusedWidget; //has keyboard (or other perif) focus + + 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; + } + } + public GraphicObject hoverWidget + { + get { return _hoverWidget; } + set { + if (_hoverWidget == value) + return; + _hoverWidget = value; + } + } + public GraphicObject FocusedWidget { + get { return _focusedWidget; } + set { + if (_focusedWidget == value) + return; + if (_focusedWidget != null) + _focusedWidget.onUnfocused (this, null); + _focusedWidget = value; + if (_focusedWidget != null) + _focusedWidget.onFocused (this, null); + } + } + #endregion + + + public void Update(){ + if (mouseRepeatCount > 0) { + int mc = mouseRepeatCount; + mouseRepeatCount -= mc; + for (int i = 0; i < mc; i++) { + FocusedWidget.onMouseClick (this, new MouseButtonEventArgs (Mouse.X, Mouse.Y, MouseButton.Left, true)); + } + } + + lock (UpdateMutex) { + processLayouting (); + + clippingRegistration (); + + processDrawing (); + } + + // if (ToolTip.isVisible) { + // ToolTip.panel.processkLayouting(); + // if (ToolTip.panel.layoutIsValid) + // ToolTip.panel.Paint(ref ctx); + // } + // Debug.WriteLine("INTERFACE: layouting: {0} ticks \t graphical update {1} ticks \t drawing {2} ticks", + // layoutTime.ElapsedTicks, + // guTime.ElapsedTicks, + // drawingTime.ElapsedTicks); + // Debug.WriteLine("INTERFACE: layouting: {0} ms \t graphical update {1} ms \t drawing {2} ms", + // layoutTime.ElapsedMilliseconds, + // guTime.ElapsedMilliseconds, + // drawingTime.ElapsedMilliseconds); + + // Debug.WriteLine("UPDATE: {0} ticks \t, {1} ms", + // updateTime.ElapsedTicks, + // updateTime.ElapsedMilliseconds); + } + void processLayouting(){ + #if MEASURE_TIME + layoutTime.Restart(); + #endif + lock (LayoutMutex) { + //Debug.WriteLine ("======= Layouting queue start ======="); + LayoutingQueueItem lqi = null; + while (Interface.CurrentInterface.LayoutingQueue.Count > 0) { + lqi = Interface.CurrentInterface.LayoutingQueue.Dequeue (); + lqi.ProcessLayouting (); + } + } + #if MEASURE_TIME + layoutTime.Stop (); + #endif + } + void clippingRegistration(){ + #if MEASURE_TIME + clippingTime.Restart (); + #endif + lock (CurrentInterface.GraphicUpdateQueue) { + while (CurrentInterface.GraphicUpdateQueue.Count > 0) { + GraphicObject g = CurrentInterface.GraphicUpdateQueue.Dequeue (); + g.bmp = null; + g.IsQueueForGraphicUpdate = false; + AddToRedrawList (g); + } + } + //Debug.WriteLine ("otd:" + gobjsToRedraw.Count.ToString () + "-"); + //final redraw clips should be added only when layout is completed among parents, + //that's why it take place in a second pass + foreach (GraphicObject p in RedrawList) { + try { + p.IsInRedrawList = false; + p.Parent.RegisterClip (p.LastPaintedSlot); + p.Parent.RegisterClip (p.getSlot ()); + } catch (Exception ex) { + Debug.WriteLine ("Error Register Clip: " + ex.ToString ()); + } + } + RedrawList.Clear (); + #if MEASURE_TIME + clippingTime.Stop (); + #endif + } + void processDrawing(){ + #if MEASURE_TIME + drawingTime.Restart(); + #endif + using (surf = new ImageSurface (bmp, Format.Argb32, ClientRectangle.Width, ClientRectangle.Height, ClientRectangle.Width * 4)) { + using (ctx = new Context (surf)){ + + + if (clipping.count > 0) { + //Link.draw (ctx); + clipping.clearAndClip(ctx); + + for (int i = GraphicObjects.Count -1; i >= 0 ; i--){ + GraphicObject p = GraphicObjects[i]; + if (!p.Visible) + continue; + if (!clipping.intersect (p.Slot)) + continue; + ctx.Save (); + + p.Paint (ref ctx); + + ctx.Restore (); + } + + #if DEBUG_CLIP_RECTANGLE + clipping.stroke (ctx, Color.Red.AdjustAlpha(0.5)); + #endif + lock (Interface.CurrentInterface.RenderMutex) { + if (IsDirty) + DirtyRect += clipping.Bounds; + else + DirtyRect = clipping.Bounds; + IsDirty = true; + + DirtyRect.Left = Math.Max (0, DirtyRect.Left); + DirtyRect.Top = Math.Max (0, DirtyRect.Top); + DirtyRect.Width = Math.Min (ClientRectangle.Width - DirtyRect.Left, DirtyRect.Width); + DirtyRect.Height = Math.Min (ClientRectangle.Height - DirtyRect.Top, DirtyRect.Height); + DirtyRect.Width = Math.Max (0, DirtyRect.Width); + DirtyRect.Height = Math.Max (0, DirtyRect.Height); + + dirtyBmp = new byte[4 * DirtyRect.Width * DirtyRect.Height]; + for (int y = 0; y < DirtyRect.Height; y++) { + Array.Copy (bmp, + ((DirtyRect.Top + y) * ClientRectangle.Width * 4) + DirtyRect.Left * 4, + dirtyBmp, y * DirtyRect.Width * 4, DirtyRect.Width * 4); + } + } + clipping.Reset (); + } + //surf.WriteToPng (@"/mnt/data/test.png"); + } + } + #if MEASURE_TIME + drawingTime.Stop (); + #endif + } + + public Rectangles clipping { + get { return _redrawClip; } + set { _redrawClip = value; } + } + public List<GraphicObject> RedrawList { + get { return _gobjsToRedraw; } + set { _gobjsToRedraw = value; } + } + public void AddWidget(GraphicObject g) + { + g.Parent = this; + GraphicObjects.Insert (0, g); + + g.RegisterForLayouting (LayoutingType.Sizing); + } + public void DeleteWidget(GraphicObject g) + { + g.Visible = false;//trick to ensure clip is added to refresh zone + g.ClearBinding(); + GraphicObjects.Remove (g); + } + public void PutOnTop(GraphicObject g) + { + if (GraphicObjects.IndexOf(g) > 0) + { + GraphicObjects.Remove(g); + GraphicObjects.Insert(0, g); + //g.registerClipRect (); + } + } + /// <summary> Remove all Graphic objects from top container </summary> + public void ClearInterface() + { + int i = 0; + while (GraphicObjects.Count>0) { + //TODO:parent is not reset to null because object will be added + //to ObjectToRedraw list, and without parent, it fails + GraphicObject g = GraphicObjects [i]; + g.Visible = false; + g.ClearBinding (); + GraphicObjects.RemoveAt (0); + } + } + public GraphicObject FindByName (string nameToFind) + { + foreach (GraphicObject w in GraphicObjects) { + GraphicObject r = w.FindByName (nameToFind); + if (r != null) + return r; + } + return null; + } + + + + 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]; + + foreach (GraphicObject g in GraphicObjects) + g.RegisterForLayouting (LayoutingType.All); + + clipping.AddRectangle (clientRectangle); + } + } + + XCursor cursor = XCursor.Default; + public MouseState Mouse; + public KeyboardState Keyboard; + Rectangle clientRectangle; + + public event EventHandler<MouseCursorChangedEventArgs> MouseCursorChanged; + public event EventHandler Quit; + + #region Mouse Handling + + public XCursor MouseCursor { + set { + if (value == cursor) + return; + cursor = value; + MouseCursorChanged.Raise (this,new MouseCursorChangedEventArgs(cursor)); + } + } + 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) { + //TODO, ensure object is still in the graphic tree + //check topmost graphicobject first + GraphicObject tmp = hoverWidget; + GraphicObject topc = null; + while (tmp is GraphicObject) { + topc = tmp; + tmp = tmp.Parent as GraphicObject; + } + int idxhw = GraphicObjects.IndexOf (topc); + if (idxhw != 0) { + int i = 0; + while (i < idxhw) { + if (GraphicObjects [i].MouseIsIn (e.Position)) { + hoverWidget.onMouseLeave (this, e); + GraphicObjects [i].checkHoverWidget (e); + return true; + } + i++; + } + } + + + if (hoverWidget.MouseIsIn (e.Position)) { + hoverWidget.checkHoverWidget (e); + return true; + } else { + hoverWidget.onMouseLeave (this, e); + //seek upward from last focused graph obj's + while (hoverWidget.Parent as GraphicObject != null) { + hoverWidget = hoverWidget.Parent as GraphicObject; + if (hoverWidget.MouseIsIn (e.Position)) { + hoverWidget.checkHoverWidget (e); + return true; + } else + hoverWidget.onMouseLeave (this, e); + } + } + } + + //top level graphic obj's parsing + for (int i = 0; i < GraphicObjects.Count; i++) { + GraphicObject g = GraphicObjects[i]; + if (g.MouseIsIn (e.Position)) { + g.checkHoverWidget (e); + 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 (this, 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.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){ +// +// } + volatile bool mouseRepeatOn; + volatile int mouseRepeatCount; + Thread mouseRepeatThread; + void mouseRepeatThreadFunc() + { + mouseRepeatOn = true; + Thread.Sleep (Interface.DeviceRepeatDelay); + while (mouseRepeatOn) { + mouseRepeatCount++; + Thread.Sleep (Interface.DeviceRepeatInterval); + } + mouseRepeatCount = 0; + } + #endregion + + #region ILayoutable implementation + public void RegisterClip(Rectangle r){ + clipping.AddRectangle (r); + } + public bool ArrangeChildren { get { return false; }} + public int LayoutingTries { + get { throw new NotImplementedException (); } + set { throw new NotImplementedException (); } + } + public LayoutingType RegisteredLayoutings { + get { return LayoutingType.None; } + set { throw new NotImplementedException (); } + } + public void RegisterForLayouting (LayoutingType layoutType) { throw new NotImplementedException (); } + public bool UpdateLayout (LayoutingType layoutType) { throw new NotImplementedException (); } + public Rectangle ContextCoordinates (Rectangle r) => r; + public Rectangle ScreenCoordinates (Rectangle r) => r; + + public ILayoutable Parent { + get { return null; } + set { throw new NotImplementedException (); } + } + public ILayoutable LogicalParent { + get { return null; } + set { throw new NotImplementedException (); } + } + + public Rectangle ClientRectangle { + get { return clientRectangle; } + } + public Interface HostContainer { + get { return this; } + } + public Rectangle getSlot () => ClientRectangle; + public Rectangle getBounds () => ClientRectangle; #endregion } } |