// // GraphicObject.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.ComponentModel; using System.Xml.Serialization; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using Cairo; using System.Linq; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; namespace Crow { [StructLayout(LayoutKind.Sequential)] unsafe public struct NativeGO { public NativeGO* Parent; public int Left; public int Top; public Measure Width; public Measure Height; public int Margin; public Size MinimumSize; public Size MaximumSize; byte visible; public LayoutingType RegisteredLayoutings; /// /// Current size and position computed during layouting pass /// public Rectangle Slot; /// /// keep last slot components for each layouting pass to track /// changes and trigger update of other component accordingly /// public Rectangle LastSlot; /// /// keep last slot painted on screen to clear traces if moved or resized /// TODO: we should ensure the whole parsed widget tree is the last painted /// version to clear effective oldslot if parents have been moved or resized. /// IDEA is to add a ScreenCoordinates function that use only lastPaintedSlots /// public Rectangle LastPaintedSlot; public VerticalAlignment VerticalAlignment; public HorizontalAlignment HorizontalAlignment; unsafe public bool Visible { get { return visible > 0; } set { if (value) visible = 1; else visible = 0; } } } public class GraphicObject : IValueChange, IDisposable { #region PINVOKE const string lib = "/home/jp/devel/testsharedlib/bin/Debug/libcrow.so"; [DllImport(lib)] unsafe static extern NativeGO* CreateGO(); [DllImport(lib)] unsafe static extern void DestroyGO(NativeGO* go); #endregion internal static ulong currentUid = 0; internal ulong uid = 0; unsafe internal NativeGO* nativeHnd; #region IValueChange implementation public event EventHandler ValueChanged; public virtual void NotifyValueChanged(string MemberName, object _value) { //Debug.WriteLine ("Value changed: {0}->{1} = {2}", this, MemberName, _value); ValueChanged.Raise(this, new ValueChangeEventArgs(MemberName, _value)); } #endregion #region CTOR public GraphicObject () { unsafe { nativeHnd = CreateGO (); } #if DEBUG uid = currentUid; currentUid++; #endif } #endregion /// /// Initialize this Graphic object instance by setting style and default values and loading template if required /// public virtual void Initialize(){ if (currentInterface == null) currentInterface = Interface.CurrentInterface; loadDefaultValues (); } #region private fields Interface currentInterface = null; GraphicObject logicalParent; GraphicObject parent; string name; Fill background = Color.Transparent; Fill foreground = Color.White; Font font = "droid, 10"; double cornerRadius = 0; bool focusable = false; bool hasFocus = false; bool isActive = false; bool mouseRepeat; bool isEnabled = true; bool cacheEnabled = false; bool clipToClientRect = true; protected object dataSource; string style; object tag; #endregion #region public fields /// /// The clipping rectangles list /// public Region Clipping = new Region(); /// Prevent requeuing multiple times the same widget public bool IsQueueForRedraw = false; /// drawing Cache bitmap public byte[] bmp; /// if true, content has to be recreated public bool IsDirty = true; /// /// This size is computed on each child' layout changes. /// In stacking widget, it is used to compute the remaining space for the stretched /// widget inside the stack, which is never added to the contentSize, instead, its size /// is deducted from (parent.ClientRectangle - contentSize) /// internal Size contentSize; #endregion #region ILayoutable [XmlIgnore]unsafe public LayoutingType RegisteredLayoutings { get { return nativeHnd->RegisteredLayoutings; } set { nativeHnd->RegisteredLayoutings = value; } } //TODO: it would save the recurent cost of a cast in event bubbling if parent type was GraphicObject // or we could add to the interface the mouse events /// /// Parent in the graphic tree, used for rendering and layouting /// [XmlIgnore]public virtual GraphicObject Parent { get { return parent; } set { if (parent == value) return; DataSourceChangeEventArgs e = new DataSourceChangeEventArgs (parent, value); lock (this) { parent = value; //nativeHnd->Parent = value?.nativeHnd; } onParentChanged (this, e); } } [XmlIgnore]public GraphicObject LogicalParent { get { return logicalParent == null ? Parent : logicalParent; } set { if (logicalParent == value) return; if (logicalParent != null) logicalParent.DataSourceChanged -= onLogicalParentDataSourceChanged; DataSourceChangeEventArgs dsce = new DataSourceChangeEventArgs (LogicalParent, null); logicalParent = value; dsce.NewDataSource = LogicalParent; if (logicalParent != null) logicalParent.DataSourceChanged += onLogicalParentDataSourceChanged; onLogicalParentChanged (this, dsce); } } [XmlIgnore]unsafe public virtual Rectangle ClientRectangle { get { Rectangle cb = nativeHnd->Slot.Size; cb.Inflate ( - Margin); return cb; } } public virtual Rectangle ContextCoordinates(Rectangle r){ if (Parent is Interface) return r + Parent.ClientRectangle.Position; return parent.CacheEnabled ? r + Parent.ClientRectangle.Position : Parent.ContextCoordinates (r); } public virtual Rectangle ScreenCoordinates (Rectangle r){ return Parent.ScreenCoordinates(r) + Parent.getSlot().Position + Parent.ClientRectangle.Position; } unsafe public virtual Rectangle getSlot () { return nativeHnd->Slot;} #endregion #region EVENT HANDLERS public event EventHandler MouseWheelChanged; public event EventHandler MouseUp; public event EventHandler MouseDown; public event EventHandler MouseClick; public event EventHandler MouseDoubleClick; public event EventHandler MouseMove; public event EventHandler MouseEnter; public event EventHandler MouseLeave; public event EventHandler KeyDown; public event EventHandler KeyUp; public event EventHandler KeyPress; public event EventHandler Focused; public event EventHandler Unfocused; public event EventHandler Enabled; public event EventHandler Disabled; public event EventHandler LayoutChanged; public event EventHandler DataSourceChanged; public event EventHandler ParentChanged; public event EventHandler LogicalParentChanged; #endregion #region public properties [XmlIgnore]public Interface CurrentInterface { get { if (currentInterface == null) { currentInterface = Interface.CurrentInterface; Initialize (); } return currentInterface; } set { currentInterface = value; } } /// Random value placeholder [XmlAttributeAttribute] public object Tag { get { return tag; } set { if (tag == value) return; tag = value; NotifyValueChanged ("Tag", tag); } } /// /// If enabled, resulting bitmap of graphic object is cached in an byte array /// speeding up rendering of complex object. Default is enabled. /// [XmlAttributeAttribute][DefaultValue(true)] public virtual bool CacheEnabled { get { return cacheEnabled; } set { if (cacheEnabled == value) return; cacheEnabled = value; NotifyValueChanged ("CacheEnabled", cacheEnabled); } } /// /// If true, rendering of GraphicObject is clipped inside client rectangle /// [XmlAttributeAttribute][DefaultValue(true)] public virtual bool ClipToClientRect { get { return clipToClientRect; } set { if (clipToClientRect == value) return; clipToClientRect = value; NotifyValueChanged ("ClipToClientRect", clipToClientRect); this.RegisterForRedraw (); } } /// /// Name is used in binding to reference other GraphicObjects inside the graphic tree /// [XmlAttributeAttribute][DefaultValue(null)] public virtual string Name { get { #if DEBUG return string.IsNullOrEmpty(name) ? this.GetType().Name + uid.ToString () : name; #else return name; #endif } set { if (name == value) return; name = value; NotifyValueChanged("Name", name); } } [XmlAttributeAttribute ()][DefaultValue(VerticalAlignment.Center)] unsafe public virtual VerticalAlignment VerticalAlignment { get { return nativeHnd->VerticalAlignment; } set { if (nativeHnd->VerticalAlignment == value) return; nativeHnd->VerticalAlignment = value; NotifyValueChanged("VerticalAlignment", nativeHnd->VerticalAlignment); RegisterForLayouting (LayoutingType.Y); } } [XmlAttributeAttribute()][DefaultValue(HorizontalAlignment.Center)] unsafe public virtual HorizontalAlignment HorizontalAlignment { get { return nativeHnd->HorizontalAlignment; } set { if (nativeHnd->HorizontalAlignment == value) return; nativeHnd->HorizontalAlignment = value; NotifyValueChanged("HorizontalAlignment", nativeHnd->HorizontalAlignment); RegisterForLayouting (LayoutingType.X); } } [XmlAttributeAttribute()][DefaultValue(0)] public virtual int Left { get { unsafe { return nativeHnd->Left; } } set { if (Left == value) return; unsafe { nativeHnd->Left = value; } NotifyValueChanged ("Left", Left); this.RegisterForLayouting (LayoutingType.X); } } [XmlAttributeAttribute()][DefaultValue(0)] public virtual int Top { get { unsafe { return nativeHnd->Top; } } set { if (Top == value) return; unsafe { nativeHnd->Top = value; } NotifyValueChanged ("Top", Top); this.RegisterForLayouting (LayoutingType.Y); } } /// /// When set to True, the 's width and height will be set to Fit. /// [XmlAttributeAttribute()][DefaultValue(false)] public virtual bool Fit { get { return Width == Measure.Fit && Height == Measure.Fit ? true : false; } set { if (value == Fit) return; Width = Height = Measure.Fit; } } [XmlAttributeAttribute()][DefaultValue("Inherit")] unsafe public virtual Measure Width { get { return nativeHnd->Width.Units == Unit.Inherit ? Parent == null ? Measure.Stretched : Parent.WidthPolicy : nativeHnd->Width; } set { if (nativeHnd->Width == value) return; if (value.IsFixed) { if (value < MinimumSize.Width || (value > MaximumSize.Width && MaximumSize.Width > 0)) return; } Measure lastWP = WidthPolicy; nativeHnd->Width = value; NotifyValueChanged ("Width", nativeHnd->Width); if (WidthPolicy != lastWP) { NotifyValueChanged ("WidthPolicy", WidthPolicy); //contentSize in Stacks are only update on childLayoutChange, and the single stretched //child of the stack is not counted in contentSize, so when changing size policy of a child //we should adapt contentSize //TODO:check case when child become stretched, and another stretched item already exists. if (parent is GenericStack) {//TODO:check if I should test Group instead if ((parent as GenericStack).Orientation == Orientation.Horizontal) { if (lastWP == Measure.Fit) (parent as GenericStack).contentSize.Width -= nativeHnd->LastSlot.Width; else (parent as GenericStack).contentSize.Width += nativeHnd->LastSlot.Width; } } } this.RegisterForLayouting (LayoutingType.Width); } } [XmlAttributeAttribute()][DefaultValue("Inherit")] unsafe public virtual Measure Height { get { return nativeHnd->Height.Units == Unit.Inherit ? Parent == null ? Measure.Stretched : Parent.HeightPolicy : nativeHnd->Height; } set { if (nativeHnd->Height == value) return; if (value.IsFixed) { if (value < MinimumSize.Height || (value > MaximumSize.Height && MaximumSize.Height > 0)) return; } Measure lastHP = HeightPolicy; nativeHnd->Height = value; NotifyValueChanged ("Height", nativeHnd->Height); if (HeightPolicy != lastHP) { NotifyValueChanged ("HeightPolicy", HeightPolicy); if (parent is GenericStack) { if ((parent as GenericStack).Orientation == Orientation.Vertical) { if (lastHP == Measure.Fit) (parent as GenericStack).contentSize.Height -= nativeHnd->LastSlot.Height; else (parent as GenericStack).contentSize.Height += nativeHnd->LastSlot.Height; } } } this.RegisterForLayouting (LayoutingType.Height); } } /// /// Used for binding on dimensions, this property will never hold fixed size, but instead only /// Fit or Stretched /// [XmlIgnore]public virtual Measure WidthPolicy { get { return Width.IsFit ? Measure.Fit : Measure.Stretched; } } /// /// Used for binding on dimensions, this property will never hold fixed size, but instead only /// Fit or Stretched /// [XmlIgnore]public virtual Measure HeightPolicy { get { return Height.IsFit ? Measure.Fit : Measure.Stretched; } } [XmlAttributeAttribute()][DefaultValue(false)] public virtual bool Focusable { get { return focusable; } set { if (focusable == value) return; focusable = value; NotifyValueChanged ("Focusable", focusable); } } [XmlIgnore]public virtual bool HasFocus { get { return hasFocus; } set { if (value == hasFocus) return; hasFocus = value; if (hasFocus) onFocused (this, null); else onUnfocused (this, null); NotifyValueChanged ("HasFocus", hasFocus); } } [XmlIgnore]public virtual bool IsActive { get { return isActive; } set { if (value == isActive) return; isActive = value; NotifyValueChanged ("IsActive", isActive); } } [XmlAttributeAttribute()][DefaultValue(false)] public virtual bool MouseRepeat { get { return mouseRepeat; } set { if (mouseRepeat == value) return; mouseRepeat = value; NotifyValueChanged ("MouseRepeat", mouseRepeat); } } bool clearBackground = false; [XmlAttributeAttribute()][DefaultValue("Transparent")] public virtual Fill Background { get { return background; } set { if (background == value) return; clearBackground = false; if (value == null) return; background = value; NotifyValueChanged ("Background", background); RegisterForRedraw (); if (background is SolidColor) { if ((Background as SolidColor).Equals (Color.Clear)) clearBackground = true; } } } [XmlAttributeAttribute()][DefaultValue("White")] public virtual Fill Foreground { get { return foreground; } set { if (foreground == value) return; foreground = value; NotifyValueChanged ("Foreground", foreground); RegisterForRedraw (); } } [XmlAttributeAttribute()][DefaultValue("sans,10")] public virtual Font Font { get { return font; } set { if (value == font) return; font = value; NotifyValueChanged ("Font", font); RegisterForGraphicUpdate (); } } [XmlAttributeAttribute()][DefaultValue(0.0)] public virtual double CornerRadius { get { return cornerRadius; } set { if (value == cornerRadius) return; cornerRadius = value; NotifyValueChanged ("CornerRadius", cornerRadius); RegisterForRedraw (); } } [XmlAttributeAttribute()][DefaultValue(0)] public virtual int Margin { get { unsafe { return nativeHnd->Margin; } } set { if (value == Margin) return; unsafe { nativeHnd->Margin = value; } NotifyValueChanged ("Margin", Margin); RegisterForGraphicUpdate (); } } [XmlAttributeAttribute][DefaultValue(true)] public virtual bool Visible { get { unsafe { return nativeHnd->Visible;} } set { if (value == Visible) return; unsafe { nativeHnd->Visible = value; } RegisterForLayouting (LayoutingType.Sizing); //trigger a mouse to handle possible hover changes //CurrentInterface.ProcessMouseMove (CurrentInterface.Mouse.X, CurrentInterface.Mouse.Y); NotifyValueChanged ("Visible", Visible); } } [XmlAttributeAttribute][DefaultValue(true)] public virtual bool IsEnabled { get { return isEnabled; } set { if (value == isEnabled) return; isEnabled = value; if (isEnabled) onEnable (this, null); else onDisable (this, null); NotifyValueChanged ("IsEnabled", isEnabled); RegisterForRedraw (); } } [XmlAttributeAttribute][DefaultValue("1,1")] public virtual Size MinimumSize { get { unsafe { return nativeHnd->MinimumSize;} } set { if (value == MinimumSize) return; unsafe { nativeHnd->MinimumSize = value; } NotifyValueChanged ("MinimumSize", MinimumSize); RegisterForLayouting (LayoutingType.Sizing); } } [XmlAttributeAttribute][DefaultValue("0,0")] public virtual Size MaximumSize { get { unsafe { return nativeHnd->MaximumSize;} } set { if (value == MaximumSize) return; unsafe { nativeHnd->MaximumSize = value; } NotifyValueChanged ("MaximumSize", MaximumSize); RegisterForLayouting (LayoutingType.Sizing); } } /// /// Seek first logical tree upward if logicalParent is set, or seek graphic tree for /// a not null dataSource that will be active for all descendants having dataSource=null /// [XmlAttributeAttribute][DefaultValue(null)] public virtual object DataSource { set { if (DataSource == value) return; DataSourceChangeEventArgs dse = new DataSourceChangeEventArgs (DataSource, null); dataSource = value; dse.NewDataSource = DataSource; OnDataSourceChanged (this, dse); NotifyValueChanged ("DataSource", DataSource); } get { return dataSource == null ? LogicalParent == null ? null : LogicalParent.DataSource : dataSource; } } protected virtual void onLogicalParentDataSourceChanged(object sender, DataSourceChangeEventArgs e){ if (localDataSourceIsNull) OnDataSourceChanged (this, e); } internal bool localDataSourceIsNull { get { return dataSource == null; } } internal bool localLogicalParentIsNull { get { return logicalParent == null; } } public virtual void OnDataSourceChanged(object sender, DataSourceChangeEventArgs e){ DataSourceChanged.Raise (this, e); #if DEBUG_BINDING Debug.WriteLine("New DataSource for => {0} \n\t{1}=>{2}", this.ToString(),e.OldDataSource,e.NewDataSource); #endif } [XmlAttributeAttribute] public virtual string Style { get { return style; } set { if (value == style) return; style = value; NotifyValueChanged ("Style", style); } } #endregion #region Default and Style Values loading /// Loads the default values from XML attributes default public void loadDefaultValues() { #if DEBUG_LOAD Debug.WriteLine ("LoadDefValues for " + this.ToString ()); #endif Type thisType = this.GetType (); if (!string.IsNullOrEmpty (Style)) { if (Interface.DefaultValuesLoader.ContainsKey (Style)) { Interface.DefaultValuesLoader [Style] (this); return; } } else { if (Interface.DefaultValuesLoader.ContainsKey (thisType.FullName)) { Interface.DefaultValuesLoader [thisType.FullName] (this); return; } else if (!Interface.Styling.ContainsKey (thisType.FullName)) { if (Interface.DefaultValuesLoader.ContainsKey (thisType.Name)) { Interface.DefaultValuesLoader [thisType.Name] (this); return; } } } List