From: Jean-Philippe Bruyère Date: Sat, 5 Jun 2021 14:52:53 +0000 (+0200) Subject: ItemsScroller for list, layouting changes, editor suggestions X-Git-Tag: v0.9.5-beta~19 X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=f738cf0f78c57750cce8fd5744825b0591f24bb8;p=jp%2Fcrow.git ItemsScroller for list, layouting changes, editor suggestions --- diff --git a/Crow/Templates/ScrollingListBox.template b/Crow/Templates/ScrollingListBox.template index 1e7304f0..a5a9b629 100644 --- a/Crow/Templates/ScrollingListBox.template +++ b/Crow/Templates/ScrollingListBox.template @@ -1,14 +1,14 @@ - - + diff --git a/Crow/src/DebugUtils/DebugLogger.cs b/Crow/src/DebugUtils/DebugLogger.cs index 058921aa..2a9b75bb 100644 --- a/Crow/src/DebugUtils/DebugLogger.cs +++ b/Crow/src/DebugUtils/DebugLogger.cs @@ -24,8 +24,7 @@ namespace Crow #if DEBUG_LOG static bool logevt (DbgEvtType evtType) //=> IncludeEvents != DbgEvtType.None && (evtType & DiscardEvents) == 0 && (evtType & IncludeEvents) == IncludeEvents; - /*=> IncludeEvents == DbgEvtType.All || - IncludeEvents != DbgEvtType.None && (evtType & DiscardEvents) == 0 && (evtType & IncludeEvents) == IncludeEvents;*/ + //=> IncludeEvents != DbgEvtType.None && (evtType & DiscardEvents) == 0 && (evtType & IncludeEvents) == IncludeEvents; => IncludeEvents == DbgEvtType.All || (IncludeEvents != DbgEvtType.None && (evtType & IncludeEvents) == IncludeEvents); diff --git a/Crow/src/IML/Instantiator.cs b/Crow/src/IML/Instantiator.cs index b29912bc..ee1b615f 100644 --- a/Crow/src/IML/Instantiator.cs +++ b/Crow/src/IML/Instantiator.cs @@ -214,7 +214,7 @@ namespace Crow.IML { while (reader.NodeType != XmlNodeType.Element) reader.Read (); root = reader.Name; - Type t = tryGetGOType (root); + Type t = GetWidgetTypeFromName (root); if (t == null) throw new Exception ("IML parsing error: undefined root type (" + root + ")"); return t; @@ -547,7 +547,7 @@ namespace Crow.IML { ctx.il.Emit (OpCodes.Ldloc_0); ctx.il.Emit (OpCodes.Ldloc_0); - Type t = tryGetGOType (reader.Name); + Type t = GetWidgetTypeFromName (reader.Name); if (t == null) throw new Exception (reader.Name + " type not found"); ConstructorInfo ci = t.GetConstructor ( @@ -1609,7 +1609,7 @@ namespace Crow.IML { /// /// the corresponding type object /// graphic object type name without its namespace - internal static Type tryGetGOType (string typeName){ + public static Type GetWidgetTypeFromName (string typeName){ if (knownGOTypes.ContainsKey (typeName)) return knownGOTypes [typeName]; Type t = Type.GetType ("Crow." + typeName); diff --git a/Crow/src/Interface.cs b/Crow/src/Interface.cs index 60f0e221..3466b33b 100644 --- a/Crow/src/Interface.cs +++ b/Crow/src/Interface.cs @@ -523,15 +523,12 @@ namespace Crow public Dictionary StylingConstants; /// parse all styling data's during application startup and build global Styling Dictionary protected virtual void loadStyling() { - //fetch styling info in this order, if member styling is alreadey referenced in previous - //assembly, it's ignored. - loadThemeStyle (); - loadStylingFromAssembly (Assembly.GetExecutingAssembly ()); foreach (Assembly a in crowAssemblies) { loadStylingFromAssembly (a); } loadStylingFromAssembly (Assembly.GetEntryAssembly ()); + loadThemeStyle (); } /// Search for .style resources in assembly protected void loadStylingFromAssembly (Assembly assembly) { diff --git a/Crow/src/Text/Encoding.cs b/Crow/src/Text/Encoding.cs index b02e4565..ac730650 100644 --- a/Crow/src/Text/Encoding.cs +++ b/Crow/src/Text/Encoding.cs @@ -44,6 +44,7 @@ namespace Crow.Text } throw new NotImplementedException(); } + buff[encodedBytes] = 0; return encodedBytes; } diff --git a/Crow/src/Widgets/Group.cs b/Crow/src/Widgets/Group.cs index 55cc6353..9e78f187 100644 --- a/Crow/src/Widgets/Group.cs +++ b/Crow/src/Widgets/Group.cs @@ -62,6 +62,7 @@ namespace Crow childrenRWLock.ExitWriteLock (); + //largestChild = tallestChild = null; if (g.LastSlots.Width > contentSize.Width) { largestChild = g; contentSize.Width = g.LastSlots.Width; @@ -70,9 +71,9 @@ namespace Crow tallestChild = g; contentSize.Height = g.LastSlots.Height; } - g.LayoutChanged += OnChildLayoutChanges; + g.RegisteredLayoutings = LayoutingType.None; g.RegisterForLayouting (LayoutingType.Sizing | LayoutingType.ArrangeChildren); } public override void ClearChildren() @@ -99,10 +100,10 @@ namespace Crow { if (Children.Count > 0) { if (lt == LayoutingType.Width) { - if (largestChild == null) + //if (largestChild == null) searchLargestChild (); } else { - if (tallestChild == null) + //if (tallestChild == null) searchTallestChild (); } } @@ -110,7 +111,9 @@ namespace Crow } public override void OnLayoutChanges (LayoutingType layoutType) - { + { + /*if (!IsVisible) + return;*/ base.OnLayoutChanges (layoutType); childrenRWLock.EnterReadLock (); @@ -194,7 +197,7 @@ namespace Crow int cw = 0; if (forceMeasure) cw = Children [i].measureRawSize (LayoutingType.Width); - else if (Children [i].RegisteredLayoutings.HasFlag (LayoutingType.Width)) + else if (Children[i].Width.IsRelativeToParent || Children [i].RegisteredLayoutings.HasFlag (LayoutingType.Width)) continue; else cw = Children [i].Slot.Width; @@ -224,7 +227,7 @@ namespace Crow int ch = 0; if (forceMeasure) ch = Children [i].measureRawSize (LayoutingType.Height); - else if (Children [i].RegisteredLayoutings.HasFlag (LayoutingType.Height)) + else if (Children[i].Height.IsRelativeToParent || Children [i].RegisteredLayoutings.HasFlag (LayoutingType.Height)) continue; else ch = Children [i].Slot.Height; diff --git a/Crow/src/Widgets/Label.cs b/Crow/src/Widgets/Label.cs index c8b4b238..47082698 100644 --- a/Crow/src/Widgets/Label.cs +++ b/Crow/src/Widgets/Label.cs @@ -485,6 +485,14 @@ namespace Crow int hoverLine = _multiline ? (int)Math.Min (Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent + fe.Descent))), lines.Count - 1) : 0; hoverLoc = new CharLocation (hoverLine, -1, mouseLocalPos.X); + using (Context gr = new Context (IFace.surf)) { + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + gr.FontOptions = Interface.FontRenderingOptions; + gr.Antialias = Interface.Antialias; + + updateLocation (gr, ClientRectangle.Width, ref hoverLoc); + } } protected virtual bool cancelLinePrint (double lineHeght, double y, int clientHeight) => false; RectangleD? textCursor = null; diff --git a/Crow/src/Widgets/ScrollingStack.cs b/Crow/src/Widgets/ScrollingStack.cs new file mode 100644 index 00000000..959a4550 --- /dev/null +++ b/Crow/src/Widgets/ScrollingStack.cs @@ -0,0 +1,210 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) + +using System; +using System.ComponentModel; +using Crow.Cairo; + +namespace Crow { + public class ScrollingStack : GenericStack { + int scroll, maxScroll, itemSize = -1, visibleItems; + + [DefaultValue (0)]public virtual int Scroll { + get { return scroll; } + set { + //cancelAdjustScroll = true; + + if (scroll == value) + return; + + int newS = value; + if (newS < 0) + newS = 0; + else if (newS > maxScroll) + newS = maxScroll; + + if (newS == scroll) + return; + + scroll = newS; + + NotifyValueChangedAuto (scroll); + RegisterForGraphicUpdate (); + } + } + [DefaultValue (0)]public virtual int MaxScroll { + get { return maxScroll; } + set { + if (maxScroll == value) + return; + + maxScroll = Math.Max (0, value); + + if (scroll > maxScroll) + Scroll = maxScroll; + + NotifyValueChangedAuto (maxScroll); + //RegisterForGraphicUpdate (); + } + } + + + + public override void RemoveChild(Widget child){ + base.RemoveChild (child); + updateMaxScroll (); + } + public override void InsertChild (int idx, Widget g) { + base.InsertChild (idx, g); + updateMaxScroll (); + } + public override void ClearChildren() { + MaxScroll = 0; + itemSize = -1; + visibleItems = 0; + base.ClearChildren (); + } + + public override void OnLayoutChanges(LayoutingType layoutType) + { + if (Orientation == Orientation.Horizontal) { + if (layoutType == LayoutingType.Width && itemSize > 0) { + visibleItems = (int)Math.Ceiling ((double)ClientRectangle.Width / itemSize); + updateMaxScroll (); + } + } else if (layoutType == LayoutingType.Height && itemSize > 0) { + visibleItems = (int)Math.Ceiling ((double)ClientRectangle.Height / itemSize); + updateMaxScroll (); + } + base.OnLayoutChanges(layoutType); + } + public override void OnChildLayoutChanges (object sender, LayoutingEventArgs arg) { + DbgLogger.StartEvent(DbgEvtType.GOOnChildLayoutChange, this); + Widget go = sender as Widget; + + if (Orientation == Orientation.Horizontal) { + if (arg.LayoutType == LayoutingType.Width && itemSize < go.Slot.Width) + itemSize = go.Slot.Width; + } else if (arg.LayoutType == LayoutingType.Height && itemSize < go.Slot.Height) + itemSize = go.Slot.Height; + + base.OnChildLayoutChanges (sender, arg); + DbgLogger.EndEvent(DbgEvtType.GOOnChildLayoutChange); + } + + void updateMaxScroll () { + if (visibleItems <= 0) + return; + MaxScroll = Children.Count - visibleItems; + NotifyValueChanged ("PageSize", visibleItems); + NotifyValueChanged ("ChildRatio", Math.Min (1.0, (double)Children.Count / visibleItems)); + } + + protected override void onDraw (Context gr) + { + DbgLogger.StartEvent (DbgEvtType.GODraw, this); + + base.onDraw (gr); + + if (ClipToClientRect) { + gr.Save (); + //clip to client zone + CairoHelpers.CairoRectangle (gr, ClientRectangle, CornerRadius); + gr.Clip (); + } + + try + { + childrenRWLock.EnterReadLock (); + + for (int i = Scroll; i < Children.Count && i < Scroll + visibleItems; i++) { + if (!Children[i].IsVisible) + continue; + /*if (Clipping.Contains (c.Slot + ClientRectangle.Position) == RegionOverlap.Out) + continue;*/ + Children[i].Paint (gr); + } + + childrenRWLock.ExitReadLock (); + } + catch (System.Exception) + { + childrenRWLock.ExitReadLock (); + throw; + } + + if (ClipToClientRect) + gr.Restore (); + + DbgLogger.EndEvent (DbgEvtType.GODraw); + } + protected override void UpdateCache (Context ctx) + { + DbgLogger.StartEvent(DbgEvtType.GOUpdateCache, this); + if (!Clipping.IsEmpty) { + using (Context gr = new Context (bmp)) { + for (int i = 0; i < Clipping.NumRectangles; i++) + gr.Rectangle(Clipping.GetRectangle(i)); + gr.ClipPreserve(); + gr.Operator = Operator.Clear; + gr.Fill(); + gr.Operator = Operator.Over; + + base.onDraw (gr); + + if (ClipToClientRect) { + CairoHelpers.CairoRectangle (gr, ClientRectangle, CornerRadius); + gr.Clip (); + } + + childrenRWLock.EnterReadLock (); + + for (int i = Scroll; i < Children.Count && i < Scroll + visibleItems; i++) { + if (!Children[i].IsVisible) + continue; + /*if (Clipping.Contains (c.Slot + ClientRectangle.Position) == RegionOverlap.Out) + continue;*/ + Children[i].Paint (gr); + } + + childrenRWLock.ExitReadLock (); + + #if DEBUG_CLIP_RECTANGLE + /*gr.LineWidth = 1; + gr.SetSourceColor(Color.DarkMagenta.AdjustAlpha (0.8)); + for (int i = 0; i < Clipping.NumRectangles; i++) + gr.Rectangle(Clipping.GetRectangle(i)); + gr.Stroke ();*/ + #endif + } + DbgLogger.AddEvent (DbgEvtType.GOResetClip, this); + Clipping.Reset (); + }/*else + Console.WriteLine("GROUP REPAINT WITH EMPTY CLIPPING");*/ + paintCache (ctx, Slot + Parent.ClientRectangle.Position); + DbgLogger.EndEvent(DbgEvtType.GOUpdateCache); + } + + + public override void checkHoverWidget (MouseMoveEventArgs e) { + base.checkHoverWidget (e);//TODO:check if not possible to put it at beginning of meth to avoid doubled check to DropTarget. + if (!childrenRWLock.TryEnterReadLock (10)) + return; + for (int i = Children.Count - 1; i >= 0; i--) { + if (Children[i].MouseIsIn (e.Position)) { + Children[i].checkHoverWidget (e); + childrenRWLock.ExitReadLock (); + return; + } + } + childrenRWLock.ExitReadLock (); + } + /// Process scrolling vertically, or if shift is down, vertically + public override void onMouseWheel (object sender, MouseWheelEventArgs e) { + e.Handled = true; + Scroll += e.Delta * itemSize; + base.onMouseWheel (sender, e); + } + } +} \ No newline at end of file diff --git a/Crow/src/Widgets/TemplatedGroup.cs b/Crow/src/Widgets/TemplatedGroup.cs index 67c1f0db..f489ca96 100644 --- a/Crow/src/Widgets/TemplatedGroup.cs +++ b/Crow/src/Widgets/TemplatedGroup.cs @@ -38,6 +38,8 @@ namespace Crow { #endregion protected Group itemsContainer; + //if scroller name 'ItemsScroller' is found in template, scroll will adapt to selected items change. + protected Scroller scroller; string _itemTemplate, dataTest; #region events @@ -55,6 +57,20 @@ namespace Crow { CrowThread loadingThread = null; bool isPaged = false; + bool useLoadingThread; + /// + /// Use anothred thread for loading items, default value is true. + /// + [DefaultValue(true)] + public bool UseLoadingThread { + get => useLoadingThread; + set { + if (useLoadingThread == value) + return; + useLoadingThread = value; + NotifyValueChangedAuto (useLoadingThread); + } + } #region Templating //TODO: dont instantiate ItemTemplates if not used @@ -92,6 +108,7 @@ namespace Crow { itemsContainer = this.child.FindByName ("ItemsContainer") as Group; if (itemsContainer == null) throw new Exception ("TemplatedGroup template Must contain a Group named 'ItemsContainer'"); + scroller = child.FindByName ("ItemsScroller") as Scroller; NotifyValueChanged ("Items", Items); if (itemsContainer.Children.Count == 0) NotifyValueChanged ("HasChildren", false); @@ -131,7 +148,7 @@ namespace Crow { [XmlIgnore]public virtual object SelectedItem{ get => selectedItem; set { - if (SelectedItem == value) + if (selectedItem == value) return; if (selectedItem is ISelectable oldItem) @@ -143,10 +160,24 @@ namespace Crow { newItem.IsSelected = true; NotifyValueChanged ("SelectedItem", SelectedItem); + NotifyValueChanged ("SelectedIndex", SelectedIndex); SelectedItemChanged.Raise (this, new SelectionChangeEventArgs (SelectedItem)); } } + [XmlIgnore]public virtual int SelectedIndex{ + get => selectedItemContainer == null ? -1 : itemsContainer.Children.IndexOf (selectedItemContainer); + set { + if (SelectedIndex == value) + return; + if (value < 0 || itemsContainer.Children.Count == 0) + Li_Selected (null, null); + if (value < itemsContainer.Children.Count) + Li_Selected (itemsContainer.Children[value], null); + else + Li_Selected (itemsContainer.Children[itemsContainer.Children.Count - 1], null); + } + } [XmlIgnore]public bool HasItems { get { return Items.Count > 0; } } @@ -191,10 +222,15 @@ namespace Crow { return; } } - - loadingThread = new CrowThread (this, loading); - loadingThread.Finished += (object sender, EventArgs e) => (sender as TemplatedGroup).Loaded.Raise (sender, e); - loadingThread.Start (); + + if (useLoadingThread) { + loadingThread = new CrowThread (this, loading); + loadingThread.Finished += (object sender, EventArgs e) => (sender as TemplatedGroup).Loaded.Raise (sender, e); + loadingThread.Start (); + } else { + loading(); + Loaded.Raise (this, null); + } //NotifyValueChanged ("SelectedIndex", _selectedIndex); //NotifyValueChanged ("SelectedItem", SelectedItem); @@ -241,13 +277,9 @@ namespace Crow { } } - - protected void raiseSelectedItemChanged(){ SelectedItemChanged.Raise (this, new SelectionChangeEventArgs (SelectedItem)); } - - public virtual void AddItem(Widget g){ itemsContainer.AddChild (g); @@ -334,7 +366,7 @@ namespace Crow { loadPage (data, itemsContainer, dataTest); } catch (Exception ex) { while (Monitor.IsEntered (IFace.UpdateMutex)) - Monitor.Exit (IFace.UpdateMutex); + Monitor.Exit (IFace.UpdateMutex); while (Monitor.IsEntered (IFace.LayoutMutex)) Monitor.Exit (IFace.LayoutMutex); System.Diagnostics.Debug.WriteLine ("loading thread aborted: " + ex.ToString()); @@ -412,7 +444,7 @@ namespace Crow { foreach (object d in _data) { loadItem (d, page, _dataTest); - if (loadingThread.cancelRequested) + if (loadingThread != null && loadingThread.cancelRequested) break; } @@ -504,10 +536,27 @@ namespace Crow { { if (sender == selectedItemContainer) return; - if (selectedItemContainer is ISelectable li) - li.IsSelected = false; + if (selectedItemContainer is ISelectable lo) + lo.IsSelected = false; selectedItemContainer = sender as Widget; - SelectedItem = selectedItemContainer.DataSource; + if (selectedItemContainer is ISelectable ln) + ln.IsSelected = true; + SelectedItem = selectedItemContainer?.DataSource; + + if (scroller != null && selectedItemContainer != null && itemsContainer is GenericStack gs) { + Rectangle scrollerCb = scroller.ClientRectangle; + Rectangle cb = gs.Slot; + Rectangle rItem = selectedItemContainer.Slot + new Point (gs.Margin); + if (gs.Orientation == Orientation.Vertical) { + if (rItem.Y - scroller.ScrollY < 0) + scroller.ScrollY = rItem.Y; + else if (rItem.Bottom - scroller.ScrollY > scrollerCb.Height) + scroller.ScrollY = rItem.Bottom - scrollerCb.Height; + } else if (rItem.X - scroller.ScrollX < 0) + scroller.ScrollX = rItem.X; + else if (rItem.Right - scroller.ScrollX > scrollerCb.Width) + scroller.ScrollX = rItem.Right - scrollerCb.Width; + } SelectedItemContainerChanged.Raise (this, new SelectionChangeEventArgs (sender)); } @@ -550,8 +599,7 @@ namespace Crow { selectedItemContainer = sender as Widget; if (selectedItemContainer == null) return; - SelectedItem = selectedItemContainer.DataSource; - //SelectedIndex = items.Children.IndexOf(sender as Widget); + SelectedItem = selectedItemContainer.DataSource; } bool emitHelperIsAlreadyExpanded (Widget go){ @@ -600,5 +648,22 @@ namespace Crow { void onDatasChanged (object sender, ValueChangeEventArgs e) { } + + public override void onKeyDown(object sender, KeyEventArgs e) + { + switch (e.Key) { + case Glfw.Key.Up: + if (SelectedIndex > 0) + SelectedIndex--; + break; + case Glfw.Key.Down: + if (SelectedIndex < Items.Count - 1) + SelectedIndex++; + break; + default: + base.onKeyDown(sender, e); + break; + } + } } } diff --git a/Crow/src/Widgets/TextBox.cs b/Crow/src/Widgets/TextBox.cs index ece9adc0..2564129e 100644 --- a/Crow/src/Widgets/TextBox.cs +++ b/Crow/src/Widgets/TextBox.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2013-2020 Jean-Philippe Bruyère +// Copyright (c) 2013-2021 Jean-Philippe Bruyère // // This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) @@ -10,21 +10,21 @@ using System.ComponentModel; namespace Crow { - public class TextBox : Label - { - #region CTOR - protected TextBox () { } - public TextBox (Interface iface, string style = null) : base (iface, style) { } - #endregion - - /// - /// Validate content of the text box. Occurs in non multiline TextBox when 'Enter' key - /// is pressed. - /// - public event EventHandler Validate; - public virtual void OnValidate (Object sender, ValidateEventArgs e) { - Validate.Raise (this, e); - } + public class TextBox : Label + { + #region CTOR + protected TextBox () { } + public TextBox (Interface iface, string style = null) : base (iface, style) { } + #endregion + + /// + /// Validate content of the text box. Occurs in non multiline TextBox when 'Enter' key + /// is pressed. + /// + public event EventHandler Validate; + public virtual void OnValidate (Object sender, ValidateEventArgs e) { + Validate.Raise (this, e); + } #region Scrolling int scrollX, scrollY, maxScrollX, maxScrollY, mouseWheelSpeed; @@ -33,13 +33,13 @@ namespace Crow /// if true, key stroke are handled in derrived class /// protected bool KeyEventsOverrides = false; - bool autoAdjustScroll = false;//if scrollXY is changed directly, dont try adjust scroll to cursor + bool autoAdjustScroll = false;//if scrollXY is changed directly, dont try adjust scroll to cursor /// Horizontal Scrolling Position [DefaultValue (0)] public virtual int ScrollX { get { return scrollX; } set { - //cancelAdjustScroll = true; + //cancelAdjustScroll = true; if (scrollX == value) return; @@ -64,9 +64,9 @@ namespace Crow public virtual int ScrollY { get { return scrollY; } set { - //cancelAdjustScroll = true; + //cancelAdjustScroll = true; - if (scrollY == value) + if (scrollY == value) return; int newS = value; @@ -144,205 +144,205 @@ namespace Crow base.onMouseWheel (sender, e); } - public override void onMouseMove (object sender, MouseMoveEventArgs e) { - if (!HasFocus || !IFace.IsDown (MouseButton.Left)) { - base.onMouseMove (sender, e); + public override void onMouseMove (object sender, MouseMoveEventArgs e) { + if (!HasFocus || !IFace.IsDown (MouseButton.Left)) { + base.onMouseMove (sender, e); return; } - Rectangle cb = ClientRectangle; + Rectangle cb = ClientRectangle; - if (CurrentLoc.Value.VisualCharXPosition < scrollX) - ScrollX = (int)CurrentLoc.Value.VisualCharXPosition; - else if (CurrentLoc.Value.VisualCharXPosition > cb.Width + scrollX) - ScrollX = (int)CurrentLoc.Value.VisualCharXPosition - cb.Width; + if (CurrentLoc.Value.VisualCharXPosition < scrollX) + ScrollX = (int)CurrentLoc.Value.VisualCharXPosition; + else if (CurrentLoc.Value.VisualCharXPosition > cb.Width + scrollX) + ScrollX = (int)CurrentLoc.Value.VisualCharXPosition - cb.Width; - double lineHeight = fe.Ascent + fe.Descent; - int firstLine = (int)Math.Ceiling((double)scrollY / lineHeight); - int lastLine = (int)Math.Floor((double)(scrollY + cb.Height) / lineHeight) - 1; - //Console.WriteLine ($"current: {currentLoc.Value.Line} first:{firstLine} last:{lastLine}"); + double lineHeight = fe.Ascent + fe.Descent; + int firstLine = (int)Math.Ceiling((double)scrollY / lineHeight); + int lastLine = (int)Math.Floor((double)(scrollY + cb.Height) / lineHeight) - 1; + //Console.WriteLine ($"current: {currentLoc.Value.Line} first:{firstLine} last:{lastLine}"); - if (CurrentLoc.Value.Line < firstLine) - ScrollY = (int)(lineHeight * CurrentLoc.Value.Line); - else if (CurrentLoc.Value.Line > lastLine) - ScrollY = (int)(lineHeight * (CurrentLoc.Value.Line + 1)) - cb.Height; + if (CurrentLoc.Value.Line < firstLine) + ScrollY = (int)(lineHeight * CurrentLoc.Value.Line); + else if (CurrentLoc.Value.Line > lastLine) + ScrollY = (int)(lineHeight * (CurrentLoc.Value.Line + 1)) - cb.Height; e.Handled = true; base.onMouseMove (sender, e); - } - #endregion - public override void OnLayoutChanges (LayoutingType layoutType) { - base.OnLayoutChanges (layoutType); - updateMaxScrolls (layoutType); - } - protected override void drawContent (Context gr) { - gr.Translate (-scrollX, -scrollY); - base.drawContent (gr); - gr.Translate (scrollX, scrollY); - } - protected override bool cancelLinePrint (double lineHeght, double y, int clientHeight) => - y + lineHeght < scrollY || y - lineHeght > clientHeight + scrollY; - protected override void updateHoverLocation (Point mouseLocalPos) { - base.updateHoverLocation (mouseLocalPos + new Point (ScrollX, ScrollY)); - } - protected override void measureTextBounds (Context gr) { - base.measureTextBounds (gr); - updateMaxScrolls (LayoutingType.Height); - updateMaxScrolls (LayoutingType.Width); - } - internal override RectangleD? computeTextCursor (Rectangle cursor) { - Rectangle cb = ClientRectangle; - cursor -= new Point (scrollX, scrollY); - - if (autoAdjustScroll) { - autoAdjustScroll = false; - int goodMsrs = 0; - if (cursor.Right < 0) - ScrollX += cursor.Right; - else if (cursor.X > cb.Width) - ScrollX += cursor.X - cb.Width; - else - goodMsrs++; - - if (cursor.Y < 0) - ScrollY += cursor.Y; - else if (cursor.Bottom > cb.Height) - ScrollY += cursor.Bottom - cb.Height; - else - goodMsrs++; - - if (goodMsrs < 2) - return null; - } else if (cursor.Right < 0 || cursor.X > cb.Width || cursor.Y < 0 || cursor.Bottom > cb.Height) - return null; - - return cursor; - } - - void updateMaxScrolls (LayoutingType layout) { - Rectangle cb = ClientRectangle; - if (layout == LayoutingType.Width) { - MaxScrollX = cachedTextSize.Width - cb.Width; - NotifyValueChanged ("PageWidth", ClientRectangle.Width); - if (cachedTextSize.Width > 0) - NotifyValueChanged ("ChildWidthRatio", Math.Min (1.0, (double)cb.Width / cachedTextSize.Width)); - } else if (layout == LayoutingType.Height) { - MaxScrollY = cachedTextSize.Height - cb.Height; - NotifyValueChanged ("PageHeight", ClientRectangle.Height); - if (cachedTextSize.Height > 0) - NotifyValueChanged ("ChildHeightRatio", Math.Min (1.0, (double)cb.Height / cachedTextSize.Height)); - } - } - - #region Keyboard handling - public override void onKeyDown (object sender, KeyEventArgs e) { - Key key = e.Key; - TextSpan selection = Selection; - switch (key) { - case Key.Backspace: - if (selection.IsEmpty) { - if (selection.Start == 0) - return; - if (CurrentLoc.Value.Column == 0) { - int lbLength = lines[CurrentLoc.Value.Line - 1].LineBreakLength; - update (new TextChange (selection.Start - lbLength, lbLength, "")); - }else - update (new TextChange (selection.Start - 1, 1, "")); - } else - update (new TextChange (selection.Start, selection.Length, "")); - break; - case Key.Delete: - if (selection.IsEmpty) { - if (selection.Start == Text.Length) - return; - if (CurrentLoc.Value.Column >= lines[CurrentLoc.Value.Line].Length) - update (new TextChange (selection.Start, lines[CurrentLoc.Value.Line].LineBreakLength, "")); - else - update (new TextChange (selection.Start, 1, "")); - } else { - if (IFace.Shift) - IFace.Clipboard = SelectedText; - update (new TextChange (selection.Start, selection.Length, "")); - } - break; - case Key.Insert: - if (IFace.Shift) - update (new TextChange (selection.Start, selection.Length, IFace.Clipboard)); - else if (IFace.Ctrl && !selection.IsEmpty) - IFace.Clipboard = SelectedText; - break; - case Key.KeypadEnter: - case Key.Enter: - if (Multiline) { - if (string.IsNullOrEmpty (LineBreak)) - detectLineBreak (); - update (new TextChange (selection.Start, selection.Length, LineBreak)); - } else - OnValidate (this, new ValidateEventArgs (_text)); - break; - case Key.Escape: - selectionStart = null; - CurrentLoc = lines.GetLocation (selection.Start); - RegisterForRedraw (); - break; - case Key.Tab: - update (new TextChange (selection.Start, selection.Length, "\t")); - break; - case Key.PageUp: - checkShift (); - LineMove (-visibleLines); - RegisterForRedraw (); - break; - case Key.PageDown: - checkShift (); - LineMove (visibleLines); - RegisterForRedraw (); - break; - default: - base.onKeyDown (sender, e); - break; - } - autoAdjustScroll = true; - e.Handled = true; - } - public override void onKeyPress (object sender, KeyPressEventArgs e) { - base.onKeyPress (sender, e); - - TextSpan selection = Selection; - update (new TextChange (selection.Start, selection.Length, e.KeyChar.ToString ())); - - /*Insert (e.KeyChar.ToString()); + } + #endregion + public override void OnLayoutChanges (LayoutingType layoutType) { + base.OnLayoutChanges (layoutType); + updateMaxScrolls (layoutType); + } + protected override void drawContent (Context gr) { + gr.Translate (-scrollX, -scrollY); + base.drawContent (gr); + gr.Translate (scrollX, scrollY); + } + protected override bool cancelLinePrint (double lineHeght, double y, int clientHeight) => + y + lineHeght < scrollY || y - lineHeght > clientHeight + scrollY; + protected override void updateHoverLocation (Point mouseLocalPos) { + base.updateHoverLocation (mouseLocalPos + new Point (ScrollX, ScrollY)); + } + protected override void measureTextBounds (Context gr) { + base.measureTextBounds (gr); + updateMaxScrolls (LayoutingType.Height); + updateMaxScrolls (LayoutingType.Width); + } + internal override RectangleD? computeTextCursor (Rectangle cursor) { + Rectangle cb = ClientRectangle; + cursor -= new Point (scrollX, scrollY); + + if (autoAdjustScroll) { + autoAdjustScroll = false; + int goodMsrs = 0; + if (cursor.Right < 0) + ScrollX += cursor.Right; + else if (cursor.X > cb.Width) + ScrollX += cursor.X - cb.Width; + else + goodMsrs++; + + if (cursor.Y < 0) + ScrollY += cursor.Y; + else if (cursor.Bottom > cb.Height) + ScrollY += cursor.Bottom - cb.Height; + else + goodMsrs++; + + if (goodMsrs < 2) + return null; + } else if (cursor.Right < 0 || cursor.X > cb.Width || cursor.Y < 0 || cursor.Bottom > cb.Height) + return null; + + return cursor; + } + + void updateMaxScrolls (LayoutingType layout) { + Rectangle cb = ClientRectangle; + if (layout == LayoutingType.Width) { + MaxScrollX = cachedTextSize.Width - cb.Width; + NotifyValueChanged ("PageWidth", ClientRectangle.Width); + if (cachedTextSize.Width > 0) + NotifyValueChanged ("ChildWidthRatio", Math.Min (1.0, (double)cb.Width / cachedTextSize.Width)); + } else if (layout == LayoutingType.Height) { + MaxScrollY = cachedTextSize.Height - cb.Height; + NotifyValueChanged ("PageHeight", ClientRectangle.Height); + if (cachedTextSize.Height > 0) + NotifyValueChanged ("ChildHeightRatio", Math.Min (1.0, (double)cb.Height / cachedTextSize.Height)); + } + } + + #region Keyboard handling + public override void onKeyDown (object sender, KeyEventArgs e) { + Key key = e.Key; + TextSpan selection = Selection; + switch (key) { + case Key.Backspace: + if (selection.IsEmpty) { + if (selection.Start == 0) + return; + if (CurrentLoc.Value.Column == 0) { + int lbLength = lines[CurrentLoc.Value.Line - 1].LineBreakLength; + update (new TextChange (selection.Start - lbLength, lbLength, "")); + }else + update (new TextChange (selection.Start - 1, 1, "")); + } else + update (new TextChange (selection.Start, selection.Length, "")); + break; + case Key.Delete: + if (selection.IsEmpty) { + if (selection.Start == Text.Length) + return; + if (CurrentLoc.Value.Column >= lines[CurrentLoc.Value.Line].Length) + update (new TextChange (selection.Start, lines[CurrentLoc.Value.Line].LineBreakLength, "")); + else + update (new TextChange (selection.Start, 1, "")); + } else { + if (IFace.Shift) + IFace.Clipboard = SelectedText; + update (new TextChange (selection.Start, selection.Length, "")); + } + break; + case Key.Insert: + if (IFace.Shift) + update (new TextChange (selection.Start, selection.Length, IFace.Clipboard)); + else if (IFace.Ctrl && !selection.IsEmpty) + IFace.Clipboard = SelectedText; + break; + case Key.KeypadEnter: + case Key.Enter: + if (Multiline) { + if (string.IsNullOrEmpty (LineBreak)) + detectLineBreak (); + update (new TextChange (selection.Start, selection.Length, LineBreak)); + } else + OnValidate (this, new ValidateEventArgs (_text)); + break; + case Key.Escape: + selectionStart = null; + CurrentLoc = lines.GetLocation (selection.Start); + RegisterForRedraw (); + break; + case Key.Tab: + update (new TextChange (selection.Start, selection.Length, "\t")); + break; + case Key.PageUp: + checkShift (); + LineMove (-visibleLines); + RegisterForRedraw (); + break; + case Key.PageDown: + checkShift (); + LineMove (visibleLines); + RegisterForRedraw (); + break; + default: + base.onKeyDown (sender, e); + break; + } + autoAdjustScroll = true; + e.Handled = true; + } + public override void onKeyPress (object sender, KeyPressEventArgs e) { + base.onKeyPress (sender, e); + + TextSpan selection = Selection; + update (new TextChange (selection.Start, selection.Length, e.KeyChar.ToString ())); + + /*Insert (e.KeyChar.ToString()); SelRelease = -1; SelBegin = new Point(CurrentColumn, SelBegin.Y); RegisterForGraphicUpdate();*/ - } - #endregion - - protected void update (TextChange change) { - lock (linesMutex) { - Span tmp = stackalloc char[Text.Length + (change.ChangedText.Length - change.Length)]; - //Console.WriteLine ($"{Text.Length,-4} {change.Start,-4} {change.Length,-4} {change.ChangedText.Length,-4} tmp:{tmp.Length,-4}"); - ReadOnlySpan src = Text.AsSpan (); - src.Slice (0, change.Start).CopyTo (tmp); - change.ChangedText.AsSpan ().CopyTo (tmp.Slice (change.Start)); - src.Slice (change.End).CopyTo (tmp.Slice (change.Start + change.ChangedText.Length)); - - _text = tmp.ToString (); - lines.Update (change); - //lines.Update (_text); - selectionStart = null; - - CurrentLoc = lines.GetLocation (change.Start + change.ChangedText.Length); - textMeasureIsUpToDate = false; - IFace.forceTextCursor = true; - } - - - NotifyValueChanged ("Text", Text); - OnTextChanged (this, new TextChangeEventArgs (change)); - - RegisterForGraphicUpdate (); - } - } + } + #endregion + + protected void update (TextChange change) { + lock (linesMutex) { + Span tmp = stackalloc char[Text.Length + (change.ChangedText.Length - change.Length)]; + //Console.WriteLine ($"{Text.Length,-4} {change.Start,-4} {change.Length,-4} {change.ChangedText.Length,-4} tmp:{tmp.Length,-4}"); + ReadOnlySpan src = Text.AsSpan (); + src.Slice (0, change.Start).CopyTo (tmp); + change.ChangedText.AsSpan ().CopyTo (tmp.Slice (change.Start)); + src.Slice (change.End).CopyTo (tmp.Slice (change.Start + change.ChangedText.Length)); + + _text = tmp.ToString (); + lines.Update (change); + //lines.Update (_text); + selectionStart = null; + + CurrentLoc = lines.GetLocation (change.Start + change.ChangedText.Length); + textMeasureIsUpToDate = false; + IFace.forceTextCursor = true; + } + + + NotifyValueChanged ("Text", Text); + OnTextChanged (this, new TextChangeEventArgs (change)); + + RegisterForGraphicUpdate (); + } + } } diff --git a/Crow/src/Widgets/Widget.cs b/Crow/src/Widgets/Widget.cs index 6f0cd6c2..dbed07b7 100644 --- a/Crow/src/Widgets/Widget.cs +++ b/Crow/src/Widgets/Widget.cs @@ -922,8 +922,7 @@ namespace Crow } else { unshownPostActions (); } - RegisterForLayouting(LayoutingType.Sizing); - + RegisterForLayouting(LayoutingType.Sizing); NotifyValueChangedAuto (isVisible); } @@ -1629,9 +1628,17 @@ namespace Crow { switch (layoutType) { case LayoutingType.Width: + /*if (Parent is Widget p) { + if (p.Width.IsFit) + p.RegisterForLayouting (LayoutingType.Width); + }*/ RegisterForLayouting (LayoutingType.X); break; case LayoutingType.Height: + /*if (Parent is Widget pp) { + if (pp.Height.IsFit) + pp.RegisterForLayouting (LayoutingType.Height); + }*/ RegisterForLayouting (LayoutingType.Y); break; } diff --git a/Crow/src/styling/StyleReader.cs b/Crow/src/styling/StyleReader.cs index fb7c43cc..22be0c35 100644 --- a/Crow/src/styling/StyleReader.cs +++ b/Crow/src/styling/StyleReader.cs @@ -162,9 +162,8 @@ namespace Crow throw new ParserException (line, column, "Unexpected end of statement", resId); ReadChar (); if (targetsClasses.Count == 0) { - //style constants, only the first occurence is kept - if (!StylingConstants.ContainsKey (currentProperty)) - StylingConstants[currentProperty] = token.ToString (); + //style constants override previous values. + StylingConstants[currentProperty] = token.ToString (); curState = States.classNames; } else { foreach (string tc in targetsClasses) { diff --git a/Samples/DebugLogAnalyzer/ui/main.crow b/Samples/DebugLogAnalyzer/ui/main.crow index 16973e11..5afc6c6c 100644 --- a/Samples/DebugLogAnalyzer/ui/main.crow +++ b/Samples/DebugLogAnalyzer/ui/main.crow @@ -92,7 +92,10 @@ - + +