From: Jean-Philippe Bruyère Date: Thu, 4 Feb 2021 14:40:39 +0000 (+0100) Subject: LangVersion=>7.3, Scrolling TextBox, multi svg Part ids, remove some Focusable=true... X-Git-Tag: v0.9.5-beta~79 X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=5cbd0b6bc72f83d1bc404664069949892ccf24ca;p=jp%2Fcrow.git LangVersion=>7.3, Scrolling TextBox, multi svg Part ids, remove some Focusable=true in default style --- diff --git a/Crow/Default.style b/Crow/Default.style index db754c1b..caf37bb4 100644 --- a/Crow/Default.style +++ b/Crow/Default.style @@ -24,7 +24,7 @@ MenuBackground = "Jet"; Button, CheckBox, RadioButton, ComboBox, Expandable, MessageBox, Popper, Slider, Spinner, TextBox { - Focusable = "true"; + //Focusable = "true"; Height = "Fit"; Background = "${ControlBackground}"; CornerRadius = "${ControlCornerRadius}"; @@ -249,7 +249,7 @@ CheckBoxAlt { ArrowBut { MouseRepeat="true"; - Focusable="true"; + //Focusable="true"; Foreground="Grey"; Background="Transparent"; diff --git a/Crow/src/Fill/SvgPicture.cs b/Crow/src/Fill/SvgPicture.cs index ee6ae1ea..f461a544 100644 --- a/Crow/src/Fill/SvgPicture.cs +++ b/Crow/src/Fill/SvgPicture.cs @@ -1,28 +1,6 @@ -// -// SvgPicture.cs +// Copyright (c) 2013-2021 Jean-Philippe Bruyère // -// 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. +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) using System; using System.IO; @@ -114,7 +92,7 @@ namespace Crow /// /// drawing Backend context /// bounds of the target surface to paint - /// limit rendering to svg part named 'subPart' + /// limit rendering to this coma separated list of svg part identified with their svg 'id' attribute. public override void Paint (Interface iFace, Context gr, Rectangle rect, string subPart = "") { if (hSVG == null) @@ -142,8 +120,11 @@ namespace Crow if (string.IsNullOrEmpty (subPart)) hSVG.RenderCairo (gr); - else - hSVG.RenderCairoSub (gr, "#" + subPart); + else { + string[] parts = subPart.Split (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string p in parts) + hSVG.RenderCairoSub (gr, "#" + subPart); + } gr.Restore (); } diff --git a/Crow/src/Interface.cs b/Crow/src/Interface.cs index d22c58e9..895c2c4c 100644 --- a/Crow/src/Interface.cs +++ b/Crow/src/Interface.cs @@ -899,15 +899,30 @@ namespace Crow PerformanceMeasure.End (PerformanceMeasure.Kind.Drawing); } + drawTextCursor (ctx); + + DbgLogger.EndEvent (DbgEvtType.Drawing, true); + } + #endregion + + #region Blinking text cursor + /// + /// Text cursor blinking frequency. + /// + public static long TEXT_CURSOR_BLINK_FREQUENCY = 400; + internal Rectangle? textCursor = null;//last printed cursor, used to clear it. + internal bool forceTextCursor = true;//when true, cursor is printed even if blinkingCursor.elapsed is not reached. + Stopwatch blinkingCursor = Stopwatch.StartNew (); + void drawTextCursor (Context ctx) { if (forceTextCursor) { - if (FocusedWidget is Label lab && lab.SelectionIsEmpty) { + if (FocusedWidget is Label lab) { if (lab.DrawCursor (ctx, out Rectangle c)) { if (textCursor != null && c != textCursor.Value) RegisterClip (textCursor.Value); textCursor = c; surf.Flush (); - } else if (textCursor != null) - RegisterClip (textCursor.Value); + } else if (textCursor != null) + RegisterClip (textCursor.Value); } blinkingCursor.Restart (); forceTextCursor = false; @@ -915,27 +930,18 @@ namespace Crow RegisterClip (textCursor.Value); textCursor = null; blinkingCursor.Restart (); - } else if (FocusedWidget is Label lab && lab.SelectionIsEmpty) { + } else if (FocusedWidget is Label lab && lab.SelectionIsEmpty) { if (blinkingCursor.ElapsedMilliseconds > TEXT_CURSOR_BLINK_FREQUENCY) { if (lab.DrawCursor (ctx, out Rectangle c)) { textCursor = c; surf.Flush (); blinkingCursor.Restart (); } - } - } - - DbgLogger.EndEvent (DbgEvtType.Drawing, true); + } + } } #endregion - #region Blinking text cursor - public static long TEXT_CURSOR_BLINK_FREQUENCY = 300; - internal Rectangle? textCursor = null; - internal bool forceTextCursor = true; - Stopwatch blinkingCursor = Stopwatch.StartNew (); - #endregion - #region GraphicTree handling /// Add widget to the Graphic tree of this interface and register it for layouting public Widget AddWidget(Widget g) diff --git a/Crow/src/Text/CharLocation.cs b/Crow/src/Text/CharLocation.cs index f55dc45a..e5087222 100644 --- a/Crow/src/Text/CharLocation.cs +++ b/Crow/src/Text/CharLocation.cs @@ -31,5 +31,7 @@ namespace Crow.Text HashCode.Combine (Line, VisualCharXPosition) : HashCode.Combine (Line, Column); } - } + + public override string ToString () => $"{Line}, {Column}"; + } } diff --git a/Crow/src/Text/Encoding.cs b/Crow/src/Text/Encoding.cs index f89ae0e4..7196e44c 100644 --- a/Crow/src/Text/Encoding.cs +++ b/Crow/src/Text/Encoding.cs @@ -21,15 +21,6 @@ namespace Crow.Text c++; continue; } - /*unsafe { - fixed (void* bp = &source.GetPinnableReference()) - fixed (byte* buffer = &buff.GetPinnableReference()) - { - return Utf16toUtf8 ((byte*)bp, buffer, tabWidth); - - } - }*/ - if (source[c] < 0x80) { //1 byte buff[encodedBytes++] = (byte)source[c++]; diff --git a/Crow/src/Widgets/Label.cs b/Crow/src/Widgets/Label.cs index 451e8016..2011a751 100644 --- a/Crow/src/Widgets/Label.cs +++ b/Crow/src/Widgets/Label.cs @@ -355,8 +355,31 @@ namespace Crow System.Threading.Monitor.Exit (linesMutex); return result; } - #region GraphicObject overrides - public override int measureRawSize(LayoutingType lt) + protected virtual void measureTextBounds (Context gr) { + fe = gr.FontExtents; + te = new TextExtents (); + + cachedTextSize.Height = (int)Math.Ceiling ((fe.Ascent + fe.Descent) * Math.Max (1, lines.Count)); + + TextExtents tmp = default; + int longestLine = 0; + for (int i = 0; i < lines.Count; i++) { + if (lines[i].LengthInPixel < 0) { + if (lines[i].Length == 0) + lines.UpdateLineLengthInPixel (i, 0);// (int)Math.Ceiling (fe.MaxXAdvance); + else { + gr.TextExtents (_text.GetLine (lines[i]), Interface.TAB_SIZE, out tmp); + lines.UpdateLineLengthInPixel (i, (int)Math.Ceiling (tmp.XAdvance)); + } + } + if (lines[i].LengthInPixel > lines[longestLine].LengthInPixel) + longestLine = i; + } + cachedTextSize.Width = lines[longestLine].LengthInPixel; + textMeasureIsUpToDate = true; + } + #region GraphicObject overrides + public override int measureRawSize(LayoutingType lt) { if ((bool)lines?.IsEmpty) getLines (); @@ -370,48 +393,14 @@ namespace Crow gr.FontOptions = Interface.FontRenderingOptions; gr.Antialias = Interface.Antialias; - fe = gr.FontExtents; - te = new TextExtents (); - - cachedTextSize.Height = (int)Math.Ceiling ((fe.Ascent+fe.Descent) * Math.Max (1, lines.Count)); - - TextExtents tmp = default; - int longestLine = 0; - for (int i = 0; i < lines.Count; i++) { - if (lines[i].LengthInPixel < 0) { - if (lines[i].Length == 0) - lines.UpdateLineLengthInPixel (i, 0);// (int)Math.Ceiling (fe.MaxXAdvance); - else { - gr.TextExtents (_text.GetLine (lines[i]), Interface.TAB_SIZE, out tmp); - lines.UpdateLineLengthInPixel (i, (int)Math.Ceiling (tmp.XAdvance)); - } - } - if (lines[i].LengthInPixel > lines[longestLine].LengthInPixel) - longestLine = i; - } - cachedTextSize.Width = lines[longestLine].LengthInPixel; - textMeasureIsUpToDate = true; + measureTextBounds (gr); } } return Margin * 2 + (lt == LayoutingType.Height ? cachedTextSize.Height : cachedTextSize.Width); } - - protected override void onDraw (Context gr) - { - base.onDraw (gr); - - gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); - gr.SetFontSize (Font.Size); - gr.FontOptions = Interface.FontRenderingOptions; - gr.Antialias = Interface.Antialias; + protected virtual void drawContent (Context gr) { Rectangle cb = ClientRectangle; - if (ClipToClientRect) { - gr.Save (); - CairoHelpers.CairoRectangle (gr, cb, CornerRadius); - gr.Clip (); - } - fe = gr.FontExtents; int lineHeight = (int)(fe.Ascent + fe.Descent); @@ -423,13 +412,13 @@ namespace Crow if (selectionStart.HasValue) { updateLocation (gr, cb.Width, ref selectionStart); if (currentLoc.Value != selectionStart.Value) - selectionNotEmpty = true; + selectionNotEmpty = true; } if (selectionNotEmpty) { if (currentLoc.Value.Line < selectionStart.Value.Line) { selStart = currentLoc.Value; selEnd = selectionStart.Value; - }else if (currentLoc.Value.Line > selectionStart.Value.Line) { + } else if (currentLoc.Value.Line > selectionStart.Value.Line) { selStart = selectionStart.Value; selEnd = currentLoc.Value; } else if (currentLoc.Value.Column < selectionStart.Value.Column) { @@ -439,7 +428,7 @@ namespace Crow selStart = selectionStart.Value; selEnd = currentLoc.Value; } - }else + } else IFace.forceTextCursor = true; } @@ -451,72 +440,104 @@ namespace Crow int y = cb.Y; for (int i = 0; i < lines.Count; i++) { - int encodedBytes = -1; - if (lines[i].Length > 0) { - int size = lines[i].Length * 4 + 1; - if (bytes.Length < size) - bytes = size > 512 ? new byte[size] : stackalloc byte[size]; - - encodedBytes = Crow.Text.Encoding.ToUtf8 (_text.GetLine (lines[i]), bytes); - bytes[encodedBytes++] = 0; - - if (lines[i].LengthInPixel < 0) { - gr.TextExtents (bytes.Slice (0, encodedBytes), out extents); - lines.UpdateLineLengthInPixel (i, (int)extents.XAdvance); + if (!cancelLinePrint (lineHeight, y, cb.Height)) { + int encodedBytes = -1; + if (lines[i].Length > 0) { + int size = lines[i].Length * 4 + 1; + if (bytes.Length < size) + bytes = size > 512 ? new byte[size] : stackalloc byte[size]; + + encodedBytes = Crow.Text.Encoding.ToUtf8 (_text.GetLine (lines[i]), bytes); + bytes[encodedBytes++] = 0; + + if (lines[i].LengthInPixel < 0) { + gr.TextExtents (bytes.Slice (0, encodedBytes), out extents); + lines.UpdateLineLengthInPixel (i, (int)extents.XAdvance); + } } - } - Rectangle lineRect = new Rectangle ( - Width.IsFit && !Multiline ? cb.X : (int)getX (cb.Width, lines[i]) + cb.X, - y, lines[i].LengthInPixel, lineHeight); + Rectangle lineRect = new Rectangle ( + Width.IsFit && !Multiline ? cb.X : (int)getX (cb.Width, lines[i]) + cb.X, + y, lines[i].LengthInPixel, lineHeight); - if (encodedBytes > 0) { - gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent); - gr.ShowText (bytes.Slice (0, encodedBytes)); - } + if (encodedBytes > 0) { + gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent); + gr.ShowText (bytes.Slice (0, encodedBytes)); + } - if (HasFocus && selectionNotEmpty) { - Rectangle selRect = lineRect; - if (_multiline) { - if (i >= selStart.Line && i <= selEnd.Line) { - if (selStart.Line == selEnd.Line) { - selRect.X = (int)selStart.VisualCharXPosition + cb.X; - selRect.Width = (int)(selEnd.VisualCharXPosition - selStart.VisualCharXPosition); - } else if (i == selStart.Line) { - int newX = (int)selStart.VisualCharXPosition + cb.X; - selRect.Width -= (newX - selRect.X) - 10; - selRect.X = newX; - } else if (i == selEnd.Line) { - selRect.Width = (int)selEnd.VisualCharXPosition - selRect.X; - } else - selRect.Width += 10; + if (HasFocus && selectionNotEmpty) { + Rectangle selRect = lineRect; + if (_multiline) { + if (i >= selStart.Line && i <= selEnd.Line) { + if (selStart.Line == selEnd.Line) { + selRect.X = (int)selStart.VisualCharXPosition + cb.X; + selRect.Width = (int)(selEnd.VisualCharXPosition - selStart.VisualCharXPosition); + } else if (i == selStart.Line) { + int newX = (int)selStart.VisualCharXPosition + cb.X; + selRect.Width -= (newX - selRect.X) - 10; + selRect.X = newX; + } else if (i == selEnd.Line) { + selRect.Width = (int)selEnd.VisualCharXPosition - selRect.X; + } else + selRect.Width += 10; + } else { + y += lineHeight; + continue; + } } else { - y += lineHeight; - continue; + selRect.X = (int)selStart.VisualCharXPosition + cb.X; + selRect.Width = (int)(selEnd.VisualCharXPosition - selStart.VisualCharXPosition); } - } else { - selRect.X = (int)selStart.VisualCharXPosition + cb.X; - selRect.Width = (int)(selEnd.VisualCharXPosition - selStart.VisualCharXPosition); - } - gr.SetSource (selBackground); - gr.Rectangle (selRect); - if (encodedBytes < 0) - gr.Fill (); - else { - gr.FillPreserve (); - gr.Save (); - gr.Clip (); - gr.SetSource (SelectionForeground); - gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent); - gr.ShowText (bytes.Slice (0, encodedBytes)); - gr.Restore (); + gr.SetSource (selBackground); + gr.Rectangle (selRect); + if (encodedBytes < 0) + gr.Fill (); + else { + gr.FillPreserve (); + gr.Save (); + gr.Clip (); + gr.SetSource (SelectionForeground); + gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent); + gr.ShowText (bytes.Slice (0, encodedBytes)); + gr.Restore (); + } + Foreground.SetAsSource (IFace, gr); } - Foreground.SetAsSource (IFace, gr); } y += lineHeight; } } + } + protected virtual void updateHoverLocation (Point mouseLocalPos) { + 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); + } + + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + gr.FontOptions = Interface.FontRenderingOptions; + gr.Antialias = Interface.Antialias; + + if (!textMeasureIsUpToDate) { + lock (linesMutex) + measureTextBounds (gr); + } + + if (ClipToClientRect) { + gr.Save (); + CairoHelpers.CairoRectangle (gr, ClientRectangle, CornerRadius); + gr.Clip (); + } + + lock (linesMutex) + drawContent (gr); + if (ClipToClientRect) gr.Restore (); } @@ -546,13 +567,10 @@ namespace Crow { base.onMouseMove (sender, e); - Point mouseLocalPos = e.Position - ScreenCoordinates (Slot).TopLeft - ClientRectangle.TopLeft; - 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); + updateHoverLocation (e.Position - ScreenCoordinates (Slot).TopLeft - ClientRectangle.TopLeft); - if (HasFocus && IFace.IsDown (Glfw.MouseButton.Left)) { - currentLoc = hoverLoc; + if (HasFocus && IFace.IsDown (MouseButton.Left)) { + currentLoc = hoverLoc; RegisterForRedraw (); } } @@ -579,24 +597,20 @@ namespace Crow public override void onMouseUp (object sender, MouseButtonEventArgs e) { base.onMouseUp (sender, e); - if (e.Button != Glfw.MouseButton.Left || !HasFocus || !selectionStart.HasValue) + if (e.Button != MouseButton.Left || !HasFocus || !selectionStart.HasValue) return; - if (selectionStart.Value == currentLoc.Value) { + if (selectionStart.Value == currentLoc.Value) selectionStart = null; - //RegisterForRedraw (); - } } public override void onMouseDoubleClick (object sender, MouseButtonEventArgs e) { base.onMouseDoubleClick (sender, e); - /*if (!(this.HasFocus || _selectable)) + if (e.Button != MouseButton.Left || !HasFocus) return; - + GotoWordStart (); - SelBegin = CurrentPosition; + selectionStart = currentLoc; GotoWordEnd (); - SelRelease = CurrentPosition; - SelectionInProgress = false;*/ RegisterForRedraw (); } #endregion @@ -668,6 +682,8 @@ namespace Crow selectionStart = null; } + protected virtual bool cancelLinePrint (int lineHeght, int y, int clientHeight) => false; + #endregion /// /// location column from @@ -677,6 +693,7 @@ namespace Crow if (location == null) return; CharLocation loc = location.Value; + //Console.WriteLine ($"updateLocation: {loc} text:{_text.Length}"); if (loc.HasVisualX) return; TextLine ls = lines[loc.Line]; @@ -726,28 +743,33 @@ namespace Crow } RectangleD? textCursor = null; - internal bool DrawCursor (Context ctx, out Rectangle rect) { - if (!currentLoc.Value.HasVisualX) { + internal virtual RectangleD? computeTextCursor (Rectangle cursor) { + Rectangle cb = ClientRectangle ; + if (cursor.X > cb.Width && cursor.Y > cb.Height) + return null; + return cursor; + } + internal virtual bool DrawCursor (Context ctx, out Rectangle rect) { + if (!currentLoc.Value.HasVisualX) { ctx.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); ctx.SetFontSize (Font.Size); ctx.FontOptions = Interface.FontRenderingOptions; ctx.Antialias = Interface.Antialias; - - updateLocation (ctx, ClientRectangle.Width, ref currentLoc); + lock(linesMutex) + updateLocation (ctx, ClientRectangle.Width, ref currentLoc); textCursor = null; } - //if (textCursor == null) { - Rectangle cb = ClientRectangle; - if (currentLoc.Value.VisualCharXPosition > cb.Width) { + + int lineHeight = (int)(fe.Ascent + fe.Descent); + textCursor = computeTextCursor (new RectangleD (currentLoc.Value.VisualCharXPosition, currentLoc.Value.Line * lineHeight, 1.0, lineHeight)); + + if (textCursor == null) { rect = default; return false; } - int lineHeight = (int)(fe.Ascent + fe.Descent); - textCursor = new RectangleD (currentLoc.Value.VisualCharXPosition + cb.X + Slot.X, - cb.Y + Slot.Y + currentLoc.Value.Line * lineHeight, 1.0, lineHeight); //} - Rectangle c = ScreenCoordinates (textCursor.Value); + Rectangle c = ScreenCoordinates (textCursor.Value + Slot.Position + ClientRectangle.Position); ctx.ResetClip (); ctx.SetSource (cursorColor); ctx.LineWidth = 1.0; diff --git a/Crow/src/Widgets/ScrollBar.cs b/Crow/src/Widgets/ScrollBar.cs index ffbd023b..fd3dd244 100644 --- a/Crow/src/Widgets/ScrollBar.cs +++ b/Crow/src/Widgets/ScrollBar.cs @@ -1,6 +1,8 @@ -// 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) +using System; +using System.ComponentModel; namespace Crow { @@ -14,5 +16,33 @@ namespace Crow public ScrollBar(Interface iface, string style = null) : base (iface, style) { } #endregion - } + double cursorRatio; + /// + /// Ratio of CusorSize / CursorContainerSize, -1 if not in use. + /// + [DefaultValue(-1.0)] + public double CursorRatio { + get => cursorRatio; + set { + if (cursorRatio == value) + return; + cursorRatio = value; + updateCursor (); + } + } + + void updateCursor () { + if (cursorRatio < 0) + return; + Rectangle r = cursor.Parent.ClientRectangle; + if (Orientation == Orientation.Horizontal) + CursorSize = (int)(cursorRatio * r.Width); + else + CursorSize = (int)(cursorRatio * r.Height); + } + protected override void HandleCursorContainerLayoutChanged (object sender, LayoutingEventArgs e) { + base.HandleCursorContainerLayoutChanged (sender, e); + updateCursor (); + } + } } diff --git a/Crow/src/Widgets/Slider.cs b/Crow/src/Widgets/Slider.cs index 222b0604..58618077 100644 --- a/Crow/src/Widgets/Slider.cs +++ b/Crow/src/Widgets/Slider.cs @@ -30,7 +30,7 @@ namespace Crow updateCursorWidgetProps (); } - void HandleCursorContainerLayoutChanged (object sender, LayoutingEventArgs e) + protected virtual void HandleCursorContainerLayoutChanged (object sender, LayoutingEventArgs e) { computeCursorPosition (); } @@ -43,10 +43,10 @@ namespace Crow => RegisterForLayouting (LayoutingType.ArrangeChildren); #region private fields - int cursorSize; + int cursorSize, minimumCursorSize; Orientation _orientation; bool holdCursor = false; - Widget cursor; + protected Widget cursor; #endregion protected double unity; @@ -76,13 +76,26 @@ namespace Crow updateCursorWidgetProps (); } } + [DefaultValue (20)] + public virtual int MinimuCursorSize { + get => minimumCursorSize; + set { + if (minimumCursorSize == value) + return; + minimumCursorSize = value; + CursorSize = cursorSize;//force recheck + NotifyValueChangedAuto (minimumCursorSize); + } + } + [DefaultValue (20)] public virtual int CursorSize { - get { return cursorSize; } + get => cursorSize; set { - if (cursorSize == value) + int newCursorSize = Math.Max (MinimuCursorSize, value); + if (cursorSize == newCursorSize) return; - cursorSize = value; + cursorSize = newCursorSize; RegisterForGraphicUpdate (); NotifyValueChangedAuto (cursorSize); updateCursorWidgetProps (); diff --git a/Crow/src/Widgets/TextBox.cs b/Crow/src/Widgets/TextBox.cs index 0c060c60..95d7958d 100644 --- a/Crow/src/Widgets/TextBox.cs +++ b/Crow/src/Widgets/TextBox.cs @@ -6,6 +6,7 @@ using Crow.Cairo; using Crow.Text; using Glfw; using System; +using System.ComponentModel; namespace Crow { @@ -25,6 +26,211 @@ namespace Crow Validate.Raise (this, e); } + #region Scrolling + int scrollX, scrollY, maxScrollX, maxScrollY, mouseWheelSpeed; + + /// + /// 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 + /// Horizontal Scrolling Position + [DefaultValue (0)] + public virtual int ScrollX { + get { return scrollX; } + set { + //cancelAdjustScroll = true; + + if (scrollX == value) + return; + + int newS = value; + if (newS < 0) + newS = 0; + else if (newS > maxScrollX) + newS = maxScrollX; + + if (newS == scrollX) + return; + + scrollX = newS; + + NotifyValueChangedAuto (scrollX); + RegisterForGraphicUpdate (); + } + } + /// Vertical Scrolling Position + [DefaultValue (0)] + public virtual int ScrollY { + get { return scrollY; } + set { + //cancelAdjustScroll = true; + + if (scrollY == value) + return; + + int newS = value; + if (newS < 0) + newS = 0; + else if (newS > maxScrollY) + newS = maxScrollY; + + if (newS == scrollY) + return; + + scrollY = newS; + + NotifyValueChangedAuto (scrollY); + RegisterForGraphicUpdate (); + } + } + /// Horizontal Scrolling maximum value + [DefaultValue (0)] + public virtual int MaxScrollX { + get { return maxScrollX; } + set { + if (maxScrollX == value) + return; + + maxScrollX = Math.Max (0, value); + + if (scrollX > maxScrollX) + ScrollX = maxScrollX; + + NotifyValueChangedAuto (maxScrollX); + //RegisterForGraphicUpdate (); + } + } + /// Vertical Scrolling maximum value + [DefaultValue (0)] + public virtual int MaxScrollY { + get { return maxScrollY; } + set { + if (maxScrollY == value) + return; + + maxScrollY = Math.Max (0, value); + + if (scrollY > maxScrollY) + ScrollY = maxScrollY; + + NotifyValueChangedAuto (maxScrollY); + //RegisterForGraphicUpdate (); + } + } + /// Mouse Wheel Scrolling multiplier + [DefaultValue (5)] + public virtual int MouseWheelSpeed { + get { return mouseWheelSpeed; } + set { + if (mouseWheelSpeed == value) + return; + + mouseWheelSpeed = value; + + NotifyValueChangedAuto (mouseWheelSpeed); + } + } + + /// Process scrolling vertically, or if shift is down, vertically + public override void onMouseWheel (object sender, MouseWheelEventArgs e) { + base.onMouseWheel (sender, e); + if (IFace.Shift) + ScrollX += e.Delta * MouseWheelSpeed; + else + ScrollY -= e.Delta * MouseWheelSpeed; + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) { + base.onMouseMove (sender, e); + if (!HasFocus || !IFace.IsDown (MouseButton.Left)) + return; + 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; + + 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; + + } + #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 (int lineHeght, int 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; + } + /*internal override bool DrawCursor (Context ctx, out Rectangle rect) { + ctx.Translate (-scrollX, -scrollY); + bool result = base.DrawCursor (ctx, out rect); + ctx.Translate (scrollX, scrollY); + return result; + }*/ + + 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) { @@ -84,6 +290,7 @@ namespace Crow base.onKeyDown (sender, e); break; } + autoAdjustScroll = true; e.Handled = true; } public override void onKeyPress (object sender, KeyPressEventArgs e) { @@ -102,25 +309,28 @@ namespace Crow #endregion void update (TextChange change) { - Span tmp = stackalloc char[Text.Length + (change.ChangedText.Length - change.Length)]; - 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)); - 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 (); getLines (); selectionStart = null; + + currentLoc = lines.GetLocation (change.Start + change.ChangedText.Length); + textMeasureIsUpToDate = false; + IFace.forceTextCursor = true; } - 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/Directory.Build.props b/Directory.Build.props index c9936924..fa3323c8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,7 @@ $(SolutionDir)build\obj\$(Configuration)\ MIT Jean-Philippe Bruyère - 7.2 + 7.3 0.9.3 $(CrowVersion)-beta diff --git a/Samples/ShowCase/ui/showcase.crow b/Samples/ShowCase/ui/showcase.crow index 2447ab67..6924fb54 100644 --- a/Samples/ShowCase/ui/showcase.crow +++ b/Samples/ShowCase/ui/showcase.crow @@ -19,16 +19,14 @@