From 79a2b3682f29c58a1f33635c093cd392025831ce Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 13 Oct 2022 11:15:46 +0100 Subject: [PATCH 01/26] Support for selecting and resizing 'View' basic class --- src/Design.cs | 175 ++++++++++++++++--------------- src/UI/Editor.cs | 237 ++++++++++++++++++++++++------------------ src/ViewExtensions.cs | 55 +++++++--- src/ViewFactory.cs | 61 +++++++---- 4 files changed, 298 insertions(+), 230 deletions(-) diff --git a/src/Design.cs b/src/Design.cs index 3fd8f311..58310ec3 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -31,7 +31,7 @@ public class Design public Property? GetDesignableProperty(string propertyName) { - return GetDesignableProperties().SingleOrDefault(p=>p.PropertyInfo.Name.Equals(propertyName)); + return GetDesignableProperties().SingleOrDefault(p => p.PropertyInfo.Name.Equals(propertyName)); } @@ -40,12 +40,9 @@ public class Design /// this instance. Instead use so that /// new child controls are preserved for design time changes /// - public View View {get;} - public bool IsContainerView { get - { - return View.IsContainerView(); - } - } + public View View { get; } + public bool IsContainerView => View.IsContainerView(); + public bool IsBorderlessContainerView => View.IsBorderlessContainerView(); public Design(SourceCodeFile sourceCode, string fieldName, View view) { @@ -67,11 +64,11 @@ private void CreateSubControlDesigns(View view) { logger.Info($"Found subView of Type '{subView.GetType()}'"); - if(subView.Data is string name) + if (subView.Data is string name) { subView.Data = CreateSubControlDesign(SourceCode, name, subView); } - + CreateSubControlDesigns(subView); } } @@ -81,7 +78,7 @@ public Design CreateSubControlDesign(SourceCodeFile sourceCode, string name, Vie // HACK: if you don't pull the label out first it complains that you cant set Focusable to true // on the Label because its super is not focusable :( var super = subView.SuperView; - if(super != null) + if (super != null) { super.Remove(subView); } @@ -89,7 +86,7 @@ public Design CreateSubControlDesign(SourceCodeFile sourceCode, string name, Vie // all views can be focused so that they can be edited // or deleted etc subView.CanFocus = true; - + if (subView is TableView tv && tv.Table != null && tv.Table.Rows.Count == 0) { // add example rows so that it is easier to design the view @@ -106,7 +103,7 @@ public Design CreateSubControlDesign(SourceCodeFile sourceCode, string name, Vie // To make the graph render correctly we need some content // either a Series or an Annotation - if(subView is GraphView gv && gv.Series.Count == 0 && gv.Annotations.Count == 0) + if (subView is GraphView gv && gv.Series.Count == 0 && gv.Annotations.Count == 0) { // We don't have either so add one gv.Annotations.Add(new TextAnnotation @@ -115,34 +112,36 @@ public Design CreateSubControlDesign(SourceCodeFile sourceCode, string name, Vie Text = "" }); } - if(subView is MenuBar mb) - { + if (subView is MenuBar mb) + { MenuTracker.Instance.Register(mb); } - if(subView is CheckBox cb) + if (subView is CheckBox cb) { RegisterCheckboxDesignTimeChanges(cb); } - if(subView is TextView txt) + if (subView is TextView txt) { // prevent control from responding to events - txt.MouseClick += (s)=>s.Handled = true; - txt.KeyDown += (s)=>s.Handled = true; + txt.MouseClick += (s) => s.Handled = true; + txt.KeyDown += (s) => s.Handled = true; } - - if(subView is TreeView tree) + + if (subView is TreeView tree) { - tree.AddObject(new TreeNode("Example Branch 1"){ - Children = new []{new TreeNode("Child 1")} + tree.AddObject(new TreeNode("Example Branch 1") + { + Children = new[] { new TreeNode("Child 1") } }); - tree.AddObject(new TreeNode("Example Branch 2"){ - Children = new []{ + tree.AddObject(new TreeNode("Example Branch 2") + { + Children = new[]{ new TreeNode("Child 1"), new TreeNode("Child 2")} }); - + for (int l = 0; l < 20; l++) tree.AddObject(new TreeNode($"Example Leaf {l}")); } @@ -152,8 +151,8 @@ public Design CreateSubControlDesign(SourceCodeFile sourceCode, string name, Vie super.Add(subView); } - var d = new Design(sourceCode,name, subView); - subView.Enter += (s=>SelectionManager.Instance.SetSelection(d)); + var d = new Design(sourceCode, name, subView); + subView.Enter += (s => SelectionManager.Instance.SetSelection(d)); return d; } @@ -185,17 +184,17 @@ public bool HasKnownColorScheme() { var userDefinedColorScheme = SelectionManager.Instance.GetOriginalExplicitColorScheme(this) ?? View.GetExplicitColorScheme(); - if(userDefinedColorScheme == null) + if (userDefinedColorScheme == null) return false; // theres a color scheme defined but we aren't tracking it // so report it as inherited since it must have got it from // the API somehow - if(Colors.ColorSchemes.Values.Contains(userDefinedColorScheme)) + if (Colors.ColorSchemes.Values.Contains(userDefinedColorScheme)) return false; // it has a ColorScheme but not one we are tracking - if(ColorSchemeManager.Instance.GetNameForColorScheme(userDefinedColorScheme) == null) + if (ColorSchemeManager.Instance.GetNameForColorScheme(userDefinedColorScheme) == null) return false; return true; @@ -224,7 +223,7 @@ protected virtual IEnumerable LoadDesignableProperties() { yield return CreateProperty(nameof(View.Width)); - if(AllowChangingHeight()) + if (AllowChangingHeight()) yield return CreateProperty(nameof(View.Height)); yield return CreateProperty(nameof(View.X)); @@ -235,7 +234,7 @@ protected virtual IEnumerable LoadDesignableProperties() // its important that this comes before Text because // changing the validator clears the text - if(View is TextValidateField) + if (View is TextValidateField) { yield return CreateProperty(nameof(TextValidateField.Provider)); } @@ -250,38 +249,38 @@ protected virtual IEnumerable LoadDesignableProperties() yield return CreateProperty(nameof(TextView.WordWrap)); } - if(View is Toplevel) + if (View is Toplevel) { yield return CreateProperty(nameof(Toplevel.Modal)); } // Allow changing the FieldName on anything but root where // such an action would break things badly - if(!this.IsRoot) + if (!this.IsRoot) yield return new NameProperty(this); - yield return new Property(this,View.GetActualTextProperty()); + yield return new Property(this, View.GetActualTextProperty()); // Border properties - Most views dont have a border so Border is - if(View.Border != null) + if (View.Border != null) { - yield return CreateSubProperty(nameof(Border.BorderStyle),nameof(View.Border),View.Border); - yield return CreateSubProperty(nameof(Border.Effect3D),nameof(View.Border),View.Border); + yield return CreateSubProperty(nameof(Border.BorderStyle), nameof(View.Border), View.Border); + yield return CreateSubProperty(nameof(Border.Effect3D), nameof(View.Border), View.Border); yield return CreateSubProperty(nameof(Border.DrawMarginFrame), nameof(View.Border), View.Border); } - + yield return CreateProperty(nameof(View.TextAlignment)); if (View is Button) { yield return CreateProperty(nameof(Button.IsDefault)); } - if(View is LineView) + if (View is LineView) { yield return CreateProperty(nameof(LineView.LineRune)); yield return CreateProperty(nameof(LineView.Orientation)); } - if(View is ProgressBar) + if (View is ProgressBar) { yield return CreateProperty(nameof(ProgressBar.Fraction)); yield return CreateProperty(nameof(ProgressBar.BidirectionalMarquee)); @@ -298,7 +297,7 @@ protected virtual IEnumerable LoadDesignableProperties() { yield return CreateProperty(nameof(ListView.Source)); } - if(View is GraphView gv) + if (View is GraphView gv) { yield return CreateProperty(nameof(GraphView.GraphColor)); yield return CreateProperty(nameof(GraphView.ScrollOffset)); @@ -306,16 +305,16 @@ protected virtual IEnumerable LoadDesignableProperties() yield return CreateProperty(nameof(GraphView.MarginBottom)); yield return CreateProperty(nameof(GraphView.CellSize)); - yield return CreateSubProperty(nameof(HorizontalAxis.Visible),nameof(GraphView.AxisX),gv.AxisX); - yield return CreateSubProperty(nameof(HorizontalAxis.Increment),nameof(GraphView.AxisX),gv.AxisX); - yield return CreateSubProperty(nameof(HorizontalAxis.ShowLabelsEvery),nameof(GraphView.AxisX),gv.AxisX); - yield return CreateSubProperty(nameof(HorizontalAxis.Minimum),nameof(GraphView.AxisX),gv.AxisX); - yield return CreateSubProperty(nameof(HorizontalAxis.Text),nameof(GraphView.AxisX),gv.AxisX); - yield return CreateSubProperty(nameof(VerticalAxis.Visible),nameof(GraphView.AxisY),gv.AxisY); - yield return CreateSubProperty(nameof(VerticalAxis.Increment),nameof(GraphView.AxisY),gv.AxisY); - yield return CreateSubProperty(nameof(VerticalAxis.ShowLabelsEvery),nameof(GraphView.AxisY),gv.AxisY); - yield return CreateSubProperty(nameof(VerticalAxis.Minimum),nameof(GraphView.AxisY),gv.AxisY); - yield return CreateSubProperty(nameof(VerticalAxis.Text),nameof(GraphView.AxisY),gv.AxisY); + yield return CreateSubProperty(nameof(HorizontalAxis.Visible), nameof(GraphView.AxisX), gv.AxisX); + yield return CreateSubProperty(nameof(HorizontalAxis.Increment), nameof(GraphView.AxisX), gv.AxisX); + yield return CreateSubProperty(nameof(HorizontalAxis.ShowLabelsEvery), nameof(GraphView.AxisX), gv.AxisX); + yield return CreateSubProperty(nameof(HorizontalAxis.Minimum), nameof(GraphView.AxisX), gv.AxisX); + yield return CreateSubProperty(nameof(HorizontalAxis.Text), nameof(GraphView.AxisX), gv.AxisX); + yield return CreateSubProperty(nameof(VerticalAxis.Visible), nameof(GraphView.AxisY), gv.AxisY); + yield return CreateSubProperty(nameof(VerticalAxis.Increment), nameof(GraphView.AxisY), gv.AxisY); + yield return CreateSubProperty(nameof(VerticalAxis.ShowLabelsEvery), nameof(GraphView.AxisY), gv.AxisY); + yield return CreateSubProperty(nameof(VerticalAxis.Minimum), nameof(GraphView.AxisY), gv.AxisY); + yield return CreateSubProperty(nameof(VerticalAxis.Text), nameof(GraphView.AxisY), gv.AxisY); } if (View is Window) @@ -329,20 +328,20 @@ protected virtual IEnumerable LoadDesignableProperties() if (View is TreeView tree) { - yield return CreateSubProperty(nameof(TreeStyle.CollapseableSymbol),nameof(TreeView.Style),tree.Style); - yield return CreateSubProperty(nameof(TreeStyle.ColorExpandSymbol),nameof(TreeView.Style),tree.Style); - yield return CreateSubProperty(nameof(TreeStyle.ExpandableSymbol),nameof(TreeView.Style),tree.Style); - yield return CreateSubProperty(nameof(TreeStyle.InvertExpandSymbolColors),nameof(TreeView.Style),tree.Style); - yield return CreateSubProperty(nameof(TreeStyle.LeaveLastRow),nameof(TreeView.Style),tree.Style); - yield return CreateSubProperty(nameof(TreeStyle.ShowBranchLines),nameof(TreeView.Style),tree.Style); + yield return CreateSubProperty(nameof(TreeStyle.CollapseableSymbol), nameof(TreeView.Style), tree.Style); + yield return CreateSubProperty(nameof(TreeStyle.ColorExpandSymbol), nameof(TreeView.Style), tree.Style); + yield return CreateSubProperty(nameof(TreeStyle.ExpandableSymbol), nameof(TreeView.Style), tree.Style); + yield return CreateSubProperty(nameof(TreeStyle.InvertExpandSymbolColors), nameof(TreeView.Style), tree.Style); + yield return CreateSubProperty(nameof(TreeStyle.LeaveLastRow), nameof(TreeView.Style), tree.Style); + yield return CreateSubProperty(nameof(TreeStyle.ShowBranchLines), nameof(TreeView.Style), tree.Style); } - + if (View is TableView tv) { yield return CreateProperty(nameof(TableView.FullRowSelect)); - yield return CreateSubProperty(nameof(TableStyle.AlwaysShowHeaders),nameof(TableView.Style),tv.Style); + yield return CreateSubProperty(nameof(TableStyle.AlwaysShowHeaders), nameof(TableView.Style), tv.Style); yield return CreateSubProperty(nameof(TableStyle.ExpandLastColumn), nameof(TableView.Style), tv.Style); yield return CreateSubProperty(nameof(TableStyle.InvertSelectedCellFirstCharacter), nameof(TableView.Style), tv.Style); yield return CreateSubProperty(nameof(TableStyle.ShowHorizontalHeaderOverline), nameof(TableView.Style), tv.Style); @@ -351,7 +350,7 @@ protected virtual IEnumerable LoadDesignableProperties() yield return CreateSubProperty(nameof(TableStyle.ShowVerticalHeaderLines), nameof(TableView.Style), tv.Style); } - + if (View is TabView tabView) { yield return CreateProperty(nameof(TabView.MaxTabTextWidth)); @@ -361,7 +360,7 @@ protected virtual IEnumerable LoadDesignableProperties() yield return CreateSubProperty(nameof(TabStyle.TabsOnBottom), nameof(TabView.Style), tabView.Style); } - if(View is RadioGroup) + if (View is RadioGroup) { yield return CreateProperty(nameof(RadioGroup.RadioLabels)); } @@ -370,22 +369,22 @@ protected virtual IEnumerable LoadDesignableProperties() private bool AllowChangingHeight() { // don't support multi line buttons - if(View is Button) + if (View is Button) return false; - return true; + return true; } private Property CreateSubProperty(string name, string subObjectName, object subObject) { - return new Property(this,subObject.GetType().GetProperty(name) + return new Property(this, subObject.GetType().GetProperty(name) ?? throw new Exception($"Could not find expected Property '{name}' on Sub Object of Type '{subObject.GetType()}'") - ,subObjectName,subObject); + , subObjectName, subObject); } private Property CreateProperty(string name) { - return new Property(this,View.GetType().GetProperty(name) + return new Property(this, View.GetType().GetProperty(name) ?? throw new Exception($"Could not find expected Property '{name}' on View of Type '{View.GetType()}'")); } @@ -412,7 +411,7 @@ internal IEnumerable GetExtraOperations(Point pos) if (View is TableView tv) { DataColumn? col = null; - if(!pos.IsEmpty) + if (!pos.IsEmpty) { // TODO: Currently you have to right click in the row (body) of the table // and cannot right click the headers themselves @@ -426,16 +425,16 @@ internal IEnumerable GetExtraOperations(Point pos) yield return new RenameColumnOperation(this, col); } - if(IsContainerView || IsRoot) + if (IsContainerView || IsRoot) { - yield return new AddViewOperation(SourceCode,this); + yield return new AddViewOperation(SourceCode, this); yield return new PasteOperation(this); } else { var nearestContainer = this.View.GetNearestContainerDesign(); - if(nearestContainer != null) - yield return new AddViewOperation(SourceCode,nearestContainer); + if (nearestContainer != null) + yield return new AddViewOperation(SourceCode, nearestContainer); } yield return new DeleteViewOperation(this.View); @@ -450,9 +449,9 @@ internal IEnumerable GetExtraOperations(Point pos) yield return new MoveTabOperation(this, 1); } - if(View is MenuBar) + if (View is MenuBar) { - yield return new AddMenuOperation(this,null); + yield return new AddMenuOperation(this, null); yield return new RemoveMenuOperation(this); } } @@ -466,19 +465,19 @@ public IEnumerable GetSiblings() { // If there is no parent then we are likely the top rot Design // or an orphan. Either way we have no siblings - if(View.SuperView == null) + if (View.SuperView == null) { yield break; } - foreach(var v in View.SuperView.Subviews) + foreach (var v in View.SuperView.Subviews) { - if(v == View) + if (v == View) { continue; } - if(v.Data is Design d) + if (v.Data is Design d) { yield return d; } @@ -492,12 +491,12 @@ public IEnumerable GetSiblings() public IEnumerable GetAllDesigns() { var root = GetRootDesign(); - + // Return the root design yield return root; - + // And all child designs - foreach(var d in GetAllChildDesigns(root.View)) + foreach (var d in GetAllChildDesigns(root.View)) { yield return d; } @@ -512,13 +511,13 @@ public Design GetRootDesign() var toReturn = this; var v = View; - while(v.SuperView != null) + while (v.SuperView != null) { v = v.SuperView; - if(v.Data is Design d) + if (v.Data is Design d) { - if(d.IsRoot) + if (d.IsRoot) return d; toReturn = d; @@ -589,10 +588,10 @@ public string GetUniqueFieldName(string candidate) // what field names are already taken by other objects? var usedFieldNames = allDesigns.Select(d => d.FieldName).ToList(); - usedFieldNames.AddRange(ColorSchemeManager.Instance.Schemes.Select(k=>k.Name)); + usedFieldNames.AddRange(ColorSchemeManager.Instance.Schemes.Select(k => k.Name)); // if name is already unique thats great - if(!usedFieldNames.Contains(candidate)) + if (!usedFieldNames.Contains(candidate)) { return candidate; } @@ -615,7 +614,7 @@ public string GetUniqueFieldName(string candidate) public IEnumerable GetDependantDesigns() { var everyone = GetAllDesigns().ToArray(); - return everyone.Where(o=>DependsOnUs(o,everyone)); + return everyone.Where(o => DependsOnUs(o, everyone)); } private bool DependsOnUs(Design other, Design[] everyone) @@ -625,7 +624,7 @@ private bool DependsOnUs(Design other, Design[] everyone) return false; // if their X depends on us - if(other.View.X.GetPosType(everyone,out _,out _, out var relativeTo,out _,out _)) + if (other.View.X.GetPosType(everyone, out _, out _, out var relativeTo, out _, out _)) { if (relativeTo == this) return true; diff --git a/src/UI/Editor.cs b/src/UI/Editor.cs index 5da27472..f96ee879 100644 --- a/src/UI/Editor.cs +++ b/src/UI/Editor.cs @@ -71,14 +71,14 @@ public Editor() CanFocus = true; // If there are custom keybindings read those - if(File.Exists("Keys.yaml")) + if (File.Exists("Keys.yaml")) { var d = new Deserializer(); try { _keyMap = d.Deserialize(File.ReadAllText("Keys.yaml")); - if(_keyMap.SelectionColor != null) + if (_keyMap.SelectionColor != null) { SelectionManager.Instance.SelectedScheme = _keyMap.SelectionColor.Scheme; } @@ -86,7 +86,7 @@ public Editor() catch (Exception ex) { // if there is bad yaml use the defaults - ExceptionViewer.ShowException("Failed to read keybindings",ex); + ExceptionViewer.ShowException("Failed to read keybindings", ex); _keyMap = new KeyMap(); } } @@ -119,7 +119,7 @@ private void BuildRootMenu() // center all the commands int maxWidth = rootCommands.Max(v => v.Length); - for(int i=0;i v.Name.Equals(options.ViewType))??toCreate; + toCreate = GetSupportedRootViews().FirstOrDefault(v => v.Name.Equals(options.ViewType)) ?? toCreate; } New(toLoadOrCreate, toCreate, options.Namespace); @@ -246,29 +247,29 @@ public void Run(Options options) try { - _mouseManager.HandleMouse(m,_viewBeingEdited); + _mouseManager.HandleMouse(m, _viewBeingEdited); + - //right click - if(m.Flags.HasFlag(_keyMap.RightClick)) + if (m.Flags.HasFlag(_keyMap.RightClick)) { var hit = _viewBeingEdited.View.HitTest(m, out _, out _); - - if(hit != null) + + if (hit != null) { var d = hit.GetNearestDesign() ?? _viewBeingEdited; - if(d != null) - CreateAndShowContextMenu(m,d); + if (d != null) + CreateAndShowContextMenu(m, d); } } } catch (System.Exception ex) { - ExceptionViewer.ShowException("Error processing mouse",ex); - } + ExceptionViewer.ShowException("Error processing mouse", ex); + } }; - Application.Run(this,ErrorHandler); + Application.Run(this, ErrorHandler); Application.Shutdown(); } @@ -280,12 +281,15 @@ private void CreateAndShowContextMenu(MouseEvent? m, Design? rightClicked) var selected = SelectionManager.Instance.Selected.ToArray(); var factory = new OperationFactory( - (p, v) => { return EditDialog.GetNewValue(p.Design, p, v, out var newValue) ? newValue - : throw new OperationCanceledException(); }); + (p, v) => + { + return EditDialog.GetNewValue(p.Design, p, v, out var newValue) ? newValue + : throw new OperationCanceledException(); + }); var operations = factory .CreateOperations(selected, m, rightClicked, out string name) - .Where(o=>!o.IsImpossible) + .Where(o => !o.IsImpossible) .ToArray(); var setProps = operations.OfType(); @@ -295,15 +299,16 @@ private void CreateAndShowContextMenu(MouseEvent? m, Design? rightClicked) var othersItems = others.Select(ToMenuItem); var all = new List(); - + // only add the set properties category if there are some - if(setPropsItems.Any()) + if (setPropsItems.Any()) all.Add(new MenuBarItem(name, setPropsItems.ToArray()) { - Action = () => { + Action = () => + { if (selected.Length == 1 || rightClicked != null) ShowEditProperties(rightClicked ?? selected[0]); - } + } }); all.AddRange(othersItems.ToArray()); @@ -317,13 +322,13 @@ private void CreateAndShowContextMenu(MouseEvent? m, Design? rightClicked) if (m.HasValue) { - menu.Position = new Point(m.Value.X,m.Value.Y); + menu.Position = new Point(m.Value.X, m.Value.Y); } else { var d = SelectionManager.Instance.Selected.FirstOrDefault() ?? _viewBeingEdited; - d.View.ViewToScreen(0,0,out var x, out var y); - menu.Position = new Point(x,y); + d.View.ViewToScreen(0, 0, out var x, out var y); + menu.Position = new Point(x, y); } _menuOpen = true; @@ -350,7 +355,7 @@ private void Try(Action action) } catch (Exception ex) { - ExceptionViewer.ShowException("Operation failed",ex); + ExceptionViewer.ShowException("Operation failed", ex); } finally { @@ -362,9 +367,9 @@ public override void Redraw(Rect bounds) { base.Redraw(bounds); - + // if we are editing a view - if(_viewBeingEdited != null) + if (_viewBeingEdited != null) { if (enableShowFocused) { @@ -373,30 +378,52 @@ public override void Redraw(Rect bounds) string? toDisplay = GetLowerRightTextIfAny(); // and have a designable view focused - if(toDisplay != null) + if (toDisplay != null) { // write its name in the lower right - int y = Bounds.Height -1; - int right = bounds.Width -1; + int y = Bounds.Height - 1; + int right = bounds.Width - 1; var len = toDisplay.Length; - - for(int i=0;iv.IsBorderlessContainerView)) + { + var v = selected.View; + v.ViewToScreen(0,0,out var x1,out var y1); + v.ViewToScreen(v.Frame.Width,v.Frame.Height,out var x2, out var y2); + + for (int x = x1; x < x2; x++) + for (int y = y1; y < y2; y++) + { + if (y == y1 || y == y2 - 1 || x == x1 || x == x2 - 1) + { + var rune = (y == y2 - 1 && x == x2 - 1) ? '╬' : '.'; + + Driver.Move(x, y); + Driver.AddRune(rune); + } + } } return; @@ -406,14 +433,14 @@ public override void Redraw(Rect bounds) private string? GetLowerRightTextIfAny() { - if(_flashMessage != null) + if (_flashMessage != null) { var m = _flashMessage; _flashMessage = null; return m; } - if(MenuTracker.Instance.CurrentlyOpenMenuItem != null) + if (MenuTracker.Instance.CurrentlyOpenMenuItem != null) { return $"Selected: {MenuTracker.Instance.CurrentlyOpenMenuItem.Title}"; } @@ -422,12 +449,12 @@ public override void Redraw(Rect bounds) string name = selected.Length == 1 ? selected[0].FieldName : $"{selected.Length} objects"; - if(selected.Any()) + if (selected.Any()) { return $"Selected: {name} ({_keyMap.EditProperties} to Edit, {_keyMap.ShowHelp} for Help)"; } - return GetHelpWithEmptyFormLoaded(); + return GetHelpWithEmptyFormLoaded(); } public bool HandleKey(KeyEvent keyEvent) @@ -436,16 +463,16 @@ public bool HandleKey(KeyEvent keyEvent) if (!IsCurrentTop) return false; - if(_editting) + if (_editting) return false; - + // Give the keyboard manager first shot at consuming // this key e.g. for typing into menus / reordering menus // etc - if(_keyboardManager.HandleKey( + if (_keyboardManager.HandleKey( SelectionManager.Instance.GetSingleSelectionOrNull()?.View ?? this, keyEvent)) return true; @@ -456,7 +483,7 @@ public bool HandleKey(KeyEvent keyEvent) if (keyEvent.Key == _keyMap.ShowContextMenu && !_menuOpen) { - CreateAndShowContextMenu(null,null); + CreateAndShowContextMenu(null, null); return true; } @@ -555,7 +582,7 @@ public bool HandleKey(KeyEvent keyEvent) } if (keyEvent.Key == _keyMap.MoveUp) - { + { MoveControl(0, -1); return true; } @@ -594,7 +621,7 @@ public bool HandleKey(KeyEvent keyEvent) } catch (System.Exception ex) { - ExceptionViewer.ShowException("Error",ex); + ExceptionViewer.ShowException("Error", ex); } finally { @@ -607,11 +634,11 @@ public bool HandleKey(KeyEvent keyEvent) private void SelectAll() { - if(_viewBeingEdited == null) + if (_viewBeingEdited == null) return; var everyone = _viewBeingEdited.GetAllDesigns() - .Where(d=>!d.IsRoot) + .Where(d => !d.IsRoot) .ToArray(); SelectionManager.Instance.ForceSetSelection(everyone); @@ -625,7 +652,7 @@ private void Paste() { var paste = new PasteOperation(d); - if(paste.IsImpossible) + if (paste.IsImpossible) return; OperationManager.Instance.Do(paste); @@ -644,9 +671,9 @@ private void ShowViewSpecificOperations() if (d != null) { - var options = d.GetExtraOperations().Where(o=>!o.IsImpossible).ToArray(); + var options = d.GetExtraOperations().Where(o => !o.IsImpossible).ToArray(); - if(options.Any() && Modals.Get("Operations","Ok",options, out var selected) && selected != null) + if (options.Any() && Modals.Get("Operations", "Ok", options, out var selected) && selected != null) { OperationManager.Instance.Do(selected); } @@ -660,25 +687,25 @@ private void ShowHelp() private void MoveControl(int deltaX, int deltaY) { - DoForSelectedViews((d)=>new MoveViewOperation(d,deltaX,deltaY)); + DoForSelectedViews((d) => new MoveViewOperation(d, deltaX, deltaY)); } private void Delete() { - if(_viewBeingEdited == null) + if (_viewBeingEdited == null) return; - - if(SelectionManager.Instance.Selected.Any()) + + if (SelectionManager.Instance.Selected.Any()) { - var cmd = new DeleteViewOperation(SelectionManager.Instance.Selected.Select(d=>d.View).ToArray()); + var cmd = new DeleteViewOperation(SelectionManager.Instance.Selected.Select(d => d.View).ToArray()); OperationManager.Instance.Do(cmd); } - + } - private void DoForSelectedViews(Func operationFuc, bool allowOnRoot=false) + private void DoForSelectedViews(Func operationFuc, bool allowOnRoot = false) { - if(_viewBeingEdited == null) + if (_viewBeingEdited == null) return; var selected = SelectionManager.Instance.Selected.ToArray(); @@ -691,14 +718,14 @@ private void DoForSelectedViews(Func operationFuc, bool allow OperationManager.Instance.Do(op); } - else if(selected.Length == 1) + else if (selected.Length == 1) { var viewDesign = selected.Single(); // don't delete the root view if (viewDesign != null) { - if(viewDesign.IsRoot && !allowOnRoot) + if (viewDesign.IsRoot && !allowOnRoot) return; OperationManager.Instance.Do( @@ -712,8 +739,8 @@ private void Open() { var ofd = new OpenDialog("Open", $"Select {SourceCodeFile.ExpectedExtension} file", new List(new[] { SourceCodeFile.ExpectedExtension })); - - Application.Run(ofd,ErrorHandler); + + Application.Run(ofd, ErrorHandler); if (!ofd.Canceled) { @@ -735,7 +762,7 @@ private void Open() private bool ErrorHandler(Exception arg) { - ExceptionViewer.ShowException("Global Exception",arg); + ExceptionViewer.ShowException("Global Exception", arg); return true; } @@ -749,16 +776,18 @@ private void Open(FileInfo toOpen) Design? instance = null; SelectionManager.Instance.Clear(); - Task.Run(()=>{ - + Task.Run(() => + { + var decompiler = new CodeToView(new SourceCodeFile(toOpen)); _currentDesignerFile = decompiler.SourceFile; instance = decompiler.CreateInstance(); - }).ContinueWith((t,o)=>{ + }).ContinueWith((t, o) => + { // no longer loading - Application.MainLoop.Invoke(()=>Application.RequestStop()); + Application.MainLoop.Invoke(() => Application.RequestStop()); if (t.Exception != null) { @@ -771,15 +800,15 @@ private void Open(FileInfo toOpen) if (instance != null) ReplaceViewBeingEdited(instance); - },TaskScheduler.FromCurrentSynchronizationContext()); + }, TaskScheduler.FromCurrentSynchronizationContext()); - Application.Run(open,ErrorHandler); + Application.Run(open, ErrorHandler); } private void New() { - if(!Modals.Get("Create New View","Ok",GetSupportedRootViews(),out var selected)) + if (!Modals.Get("Create New View", "Ok", GetSupportedRootViews(), out var selected)) { return; } @@ -805,31 +834,31 @@ private void New() return; var file = new FileInfo(path); - + // Check if we are about to overwrite some files // and if so warn the user var files = new SourceCodeFile(file); var sb = new StringBuilder(); - - if(files.CsFile.Exists) + + if (files.CsFile.Exists) { sb.AppendLine(files.CsFile.Name); } - if(files.DesignerFile.Exists) + if (files.DesignerFile.Exists) { sb.AppendLine(files.DesignerFile.Name); } - if(sb.Length > 0) + if (sb.Length > 0) { - var chosen = MessageBox.Query("Overwrite Files?",$"The following files will be overwritten:{Environment.NewLine}{sb.ToString().TrimEnd()}","Ok","Cancel"); - + var chosen = MessageBox.Query("Overwrite Files?", $"The following files will be overwritten:{Environment.NewLine}{sb.ToString().TrimEnd()}", "Ok", "Cancel"); + // user cancelled - if(chosen != 0) + if (chosen != 0) return; } - New(file,selected,null); + New(file, selected, null); } catch (Exception ex) { @@ -859,11 +888,11 @@ private void New(FileInfo toOpen, Type typeToCreate, string? explicitNamespace) return; } } - + //Validate the namespace if (string.IsNullOrWhiteSpace(ns) || ns.Contains(" ") || char.IsDigit(ns.First())) { - MessageBox.ErrorQuery("Invalid Namespace","Namespace must not contain spaces, be empty or begin with a number","Ok"); + MessageBox.ErrorQuery("Invalid Namespace", "Namespace must not contain spaces, be empty or begin with a number", "Ok"); return; } @@ -875,12 +904,14 @@ private void New(FileInfo toOpen, Type typeToCreate, string? explicitNamespace) var open = new LoadingDialog(toOpen); - Task.Run(() => { + Task.Run(() => + { // Create the view files and compile instance = viewToCode.GenerateNewView(toOpen, ns ?? "YourNamespace", typeToCreate, out _currentDesignerFile); - }).ContinueWith((t, o) => { + }).ContinueWith((t, o) => + { // no longer loading Application.MainLoop.Invoke(() => Application.RequestStop()); @@ -899,7 +930,7 @@ private void New(FileInfo toOpen, Type typeToCreate, string? explicitNamespace) }, TaskScheduler.FromCurrentSynchronizationContext()); - Application.Run(open,ErrorHandler); + Application.Run(open, ErrorHandler); } private void ReplaceViewBeingEdited(Design design) @@ -944,7 +975,7 @@ private void Save() _lastSavedOperation = OperationManager.Instance.GetLastAppliedOperation()?.UniqueIdentifier; } - + public bool HasUnsavedChanges() { var savedOp = _lastSavedOperation; @@ -972,7 +1003,7 @@ private void ShowAddViewWindow() var toAddTo = SelectionManager.Instance.GetMostSelectedContainerOrNull() ?? _viewBeingEdited; OperationManager.Instance.Do( - new AddViewOperation(_currentDesignerFile,toAddTo) + new AddViewOperation(_currentDesignerFile, toAddTo) ); } @@ -989,15 +1020,15 @@ private void ShowEditProperties() private void ShowEditProperties(Design d) { var edit = new EditDialog(d); - Application.Run(edit,ErrorHandler); + Application.Run(edit, ErrorHandler); } private void ShowColorSchemes() { - if(_viewBeingEdited == null) + if (_viewBeingEdited == null) return; - + var schemes = new ColorSchemesUI(_viewBeingEdited); - Application.Run(schemes); + Application.Run(schemes); } } diff --git a/src/ViewExtensions.cs b/src/ViewExtensions.cs index a91a7ef5..7a330c18 100644 --- a/src/ViewExtensions.cs +++ b/src/ViewExtensions.cs @@ -17,14 +17,14 @@ public static class ViewExtensions /// public static IList GetActualSubviews(this View v) { - if(v is Window w) + if (v is Window w) { return w.Subviews[0].Subviews; } - if(v is TabView t) + if (v is TabView t) { - return t.Tabs.Select(tab=>tab.View).Where(v=>v!=null).ToList(); + return t.Tabs.Select(tab => tab.View).Where(v => v != null).ToList(); } return v.Subviews; @@ -115,10 +115,10 @@ public static string GetActualText(this View view) /// public static Design? GetNearestDesign(this View view) { - if(view is null) + if (view is null) return null; - if(view.Data is Design d) + if (view.Data is Design d) { return d; } @@ -183,10 +183,33 @@ public static bool IsContainerView(this View v) // TODO: are there any others? return v is TabView || v is FrameView || v is Window || type == typeof(View) || type.Name.Equals("ContentView"); } - public static View? HitTest(this View w, MouseEvent m, out bool isBorder,out bool isLowerRight, params View[] ignoring) + public static bool IsBorderlessContainerView(this View v) + { + var type = v.GetType(); + + // TODO: are there any others? + if (type == typeof(View) && v.IsBorderless()) + return true; + + return false; + } + + public static bool IsBorderless(this View v) + { + if (v.Border == null) + return true; + + if (v.Border.BorderStyle == BorderStyle.None) + return true; + + return false; + } + + + public static View? HitTest(this View w, MouseEvent m, out bool isBorder, out bool isLowerRight, params View[] ignoring) { // hide the views while we perform the hit test - foreach(View v in ignoring) + foreach (View v in ignoring) { v.Visible = false; } @@ -195,18 +218,18 @@ public static bool IsContainerView(this View v) var hit = ApplicationExtensions.FindDeepestView(w, m.X, m.Y); int resizeBoxArea = 2; - + if (hit != null) { - hit.ViewToScreen(hit.Bounds.Right, hit.Bounds.Bottom,out int lowerRightX, out int lowerRightY,true); - hit.ViewToScreen(0, 0,out int upperLeftX, out int upperLeftY,true); + hit.ViewToScreen(hit.Bounds.Right, hit.Bounds.Bottom, out int lowerRightX, out int lowerRightY, true); + hit.ViewToScreen(0, 0, out int upperLeftX, out int upperLeftY, true); isLowerRight = Math.Abs(lowerRightX - point.X) <= resizeBoxArea && Math.Abs(lowerRightY - point.Y) <= resizeBoxArea; - isBorder = - m.X == lowerRightX-1 || + isBorder = + m.X == lowerRightX - 1 || m.X == upperLeftX || - m.Y == lowerRightY-1 || + m.Y == lowerRightY - 1 || m.Y == upperLeftY; } else @@ -246,7 +269,7 @@ public static bool IntersectsScreenRect(this View v, Rect screenRect) v.ViewToScreen(0, 0, out var x0, out var y0); v.ViewToScreen(v.Bounds.Width, v.Bounds.Height, out var x1, out var y1); - return Rect.FromLTRB(x0,y0,x1,y1).IntersectsWith(screenRect); + return Rect.FromLTRB(x0, y0, x1, y1).IntersectsWith(screenRect); } @@ -257,7 +280,7 @@ public static bool IntersectsScreenRect(this View v, Rect screenRect) /// public static ColorScheme? GetExplicitColorScheme(this View v) { - var explicitColorSchemeField = typeof(View).GetField("colorScheme",System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) + var explicitColorSchemeField = typeof(View).GetField("colorScheme", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) ?? throw new Exception("ColorScheme private backing field no longer exists"); return (ColorScheme?)explicitColorSchemeField.GetValue(v); @@ -270,6 +293,6 @@ public static bool IntersectsScreenRect(this View v, Rect screenRect) /// public static IEnumerable OrderViewsByScreenPosition(IEnumerable views) { - return views.OrderBy(v=>v.Frame.Y).ThenBy(v=>v.Frame.X); + return views.OrderBy(v => v.Frame.Y).ThenBy(v => v.Frame.X); } } diff --git a/src/ViewFactory.cs b/src/ViewFactory.cs index 1542fb3b..6d4f9882 100644 --- a/src/ViewFactory.cs +++ b/src/ViewFactory.cs @@ -39,9 +39,10 @@ public View Create(Type t) return CreateMenuBar(); } - if(t == typeof(TextValidateField)) + if (t == typeof(TextValidateField)) { - return new TextValidateField{ + return new TextValidateField + { Provider = new TextRegexProvider(".*"), Text = "Heya", @@ -50,27 +51,39 @@ public View Create(Type t) }; } - if(t == typeof(ProgressBar)) + if (t == typeof(ProgressBar)) { - return new ProgressBar{ + return new ProgressBar + { Width = 10, Height = 1, Fraction = 1f }; } - if(typeof(GraphView).IsAssignableFrom(t)) + if (t == typeof(View)) { - return new GraphView{ + return new View + { + Width = 10, + Height = 5, + }; + } + + if (typeof(GraphView).IsAssignableFrom(t)) + { + return new GraphView + { Width = 20, Height = 5, - GraphColor = Attribute.Make(Color.White,Color.Black) + GraphColor = Attribute.Make(Color.White, Color.Black) }; } - if(typeof(ListView).IsAssignableFrom(t)) + if (typeof(ListView).IsAssignableFrom(t)) { - var lv = new ListView(new List{"Item1","Item2","Item3"}){ + var lv = new ListView(new List { "Item1", "Item2", "Item3" }) + { Width = 20, Height = 3, }; @@ -78,16 +91,18 @@ public View Create(Type t) return lv; } - if(t == typeof(LineView)) + if (t == typeof(LineView)) { - return new LineView(){ + return new LineView() + { Width = 8, Height = 1 }; } - if(t == typeof(TreeView)) + if (t == typeof(TreeView)) { - return new TreeView(){ + return new TreeView() + { Width = 16, Height = 5 }; @@ -113,9 +128,9 @@ public View Create(Type t) private MenuBar CreateMenuBar() { - return new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_File (F9)", new MenuItem [] { - new MenuItem ("_Do Something", "", () => {}) + return new MenuBar(new MenuBarItem[] { + new MenuBarItem ("_File (F9)", new MenuItem [] { + new MenuItem ("_Do Something", "", () => {}) }) }); } @@ -127,7 +142,7 @@ private View CreateRadioGroup() Width = 10, Height = 5, }; - group.RadioLabels = new ustring[]{"Option 1","Option 2"}; + group.RadioLabels = new ustring[] { "Option 1", "Option 2" }; return group; } @@ -150,14 +165,14 @@ private TableView CreateTableView() private TabView CreateTabView() { - var tabView = new TabView + var tabView = new TabView { Width = 50, Height = 5, }; - tabView.AddTab(new TabView.Tab("Tab1", new View{Width = Dim.Fill(),Height=Dim.Fill()}),false); - tabView.AddTab(new TabView.Tab("Tab2", new View{Width = Dim.Fill(),Height=Dim.Fill()}),false); + tabView.AddTab(new TabView.Tab("Tab1", new View { Width = Dim.Fill(), Height = Dim.Fill() }), false); + tabView.AddTab(new TabView.Tab("Tab2", new View { Width = Dim.Fill(), Height = Dim.Fill() }), false); return tabView; } @@ -176,10 +191,10 @@ internal IEnumerable GetSupportedViews() typeof(ScrollBarView), typeof(TreeView<>)}; // The generic version of TreeView - return typeof(View).Assembly.DefinedTypes.Where(t => - typeof(View).IsAssignableFrom(t) && + return typeof(View).Assembly.DefinedTypes.Where(t => + typeof(View).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && t.IsPublic ).Except(exclude) - .OrderBy(t=>t.Name).ToArray(); + .OrderBy(t => t.Name).ToArray(); } } From 693a04fce5d58e0ffeb4d128481bacf77ed57261 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 13 Oct 2022 11:42:33 +0100 Subject: [PATCH 02/26] Move border rendering to `DrawContentComplete` --- src/SelectionManager.cs | 10 ++++---- src/SelectionState.cs | 57 +++++++++++++++++++++++++++++++++++++++++ src/UI/Editor.cs | 23 ----------------- 3 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 src/SelectionState.cs diff --git a/src/SelectionManager.cs b/src/SelectionManager.cs index d09084b9..cbd6bbaf 100644 --- a/src/SelectionManager.cs +++ b/src/SelectionManager.cs @@ -17,7 +17,7 @@ public class SelectionManager /// public IReadOnlyCollection Selected => selection.AsReadOnly(); - Dictionary oldSchemes = new(); + Dictionary oldSchemes = new(); /// /// Set to true to prevent changes to the current @@ -63,7 +63,7 @@ public ColorScheme SelectedScheme public ColorScheme? GetOriginalExplicitColorScheme(Design design) { if (oldSchemes.ContainsKey(design)) - return oldSchemes[design]; + return oldSchemes[design].OriginalScheme; return null; } @@ -101,7 +101,7 @@ private void SetSelection(bool respectLock, Design[] designs) { // record the old color scheme so we can get reset it // later when it is no longer selected - oldSchemes.Add(d, d.View.GetExplicitColorScheme()); + oldSchemes.Add(d, new SelectionState(d)); // since the view is selected mark it so d.View.ColorScheme = SelectedScheme; @@ -116,7 +116,7 @@ public void UpdateKnownScheme(Design design, ColorScheme? colorScheme) { if(oldSchemes.ContainsKey(design)) { - oldSchemes[design] = colorScheme; + oldSchemes[design].OriginalScheme = colorScheme; } } @@ -130,7 +130,7 @@ public void Clear(bool respectLock = true) // reset old color schemes so views don't still look selected foreach (var kvp in oldSchemes) { - kvp.Key.View.ColorScheme = kvp.Value; + kvp.Value.Dispose(); } oldSchemes.Clear(); } diff --git a/src/SelectionState.cs b/src/SelectionState.cs new file mode 100644 index 00000000..4c96e2ab --- /dev/null +++ b/src/SelectionState.cs @@ -0,0 +1,57 @@ +using Terminal.Gui; + +namespace TerminalGuiDesigner; + +/// +/// Describes original +/// +internal class SelectionState : IDisposable +{ + public ColorScheme? OriginalScheme { get; set; } + public Design Design{ get; } + + public SelectionState(Design design) + { + Design = design; + OriginalScheme = Design.View.GetExplicitColorScheme(); + Design.View.DrawContentComplete += DrawContentComplete; + } + + private void DrawContentComplete(Rect r) + { + if(Design.View.IsBorderlessContainerView()) + { + DrawBorderlessViewFrame(r); + } + } + + private void DrawBorderlessViewFrame(Rect r) + { + var color = SelectionManager.Instance.Selected.Contains(Design) ? + SelectionManager.Instance.SelectedScheme.Normal : + Design.View.ColorScheme.Normal; + + Application.Driver.SetAttribute(color); + + var v = Design.View; + + for (int x = 0; x < r.Width ; x++) + for (int y = 0; y < r.Height; y++) + { + if (y == 0 || y == r.Height - 1 || x == 0 || x == r.Width - 1) + { + var rune = (y == r.Height - 1 && x == r.Width - 1) ? '╬' : '.'; + v.AddRune(x,y,rune); + } + } + } + + /// + /// Clears draw callbacks and resets + /// + public void Dispose() + { + Design.View.DrawContentComplete -= DrawContentComplete; + Design.View.ColorScheme = OriginalScheme; + } +} \ No newline at end of file diff --git a/src/UI/Editor.cs b/src/UI/Editor.cs index f96ee879..777e6094 100644 --- a/src/UI/Editor.cs +++ b/src/UI/Editor.cs @@ -403,29 +403,6 @@ public override void Redraw(Rect bounds) } } - // draw box around borderless controls - // switch to highlight colors (e.g. yellow on bright green) - Application.Driver.SetAttribute(SelectionManager.Instance.SelectedScheme.Normal); - - foreach (var selected in SelectionManager.Instance.Selected.Where(v=>v.IsBorderlessContainerView)) - { - var v = selected.View; - v.ViewToScreen(0,0,out var x1,out var y1); - v.ViewToScreen(v.Frame.Width,v.Frame.Height,out var x2, out var y2); - - for (int x = x1; x < x2; x++) - for (int y = y1; y < y2; y++) - { - if (y == y1 || y == y2 - 1 || x == x1 || x == x2 - 1) - { - var rune = (y == y2 - 1 && x == x2 - 1) ? '╬' : '.'; - - Driver.Move(x, y); - Driver.AddRune(rune); - } - } - } - return; } } From 81b247bcb7497994d4fc25e586b1f071b8d0a86e Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 13 Oct 2022 12:03:38 +0100 Subject: [PATCH 03/26] Always show dotted border on Borderless views --- src/Design.cs | 6 +++- src/{SelectionState.cs => DesignState.cs} | 15 ++++++---- src/SelectionManager.cs | 35 ++++------------------- src/ToCode/ColorSchemeProperty.cs | 7 ++--- src/ViewExtensions.cs | 5 ++++ tests/ColorSchemeTests.cs | 10 +++++-- 6 files changed, 33 insertions(+), 45 deletions(-) rename src/{SelectionState.cs => DesignState.cs} (75%) diff --git a/src/Design.cs b/src/Design.cs index 58310ec3..9c937f54 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -27,6 +27,9 @@ public class Design private readonly List _designableProperties; + + public DesignState State { get; } + private readonly Logger logger = LogManager.GetCurrentClassLogger(); public Property? GetDesignableProperty(string propertyName) @@ -51,6 +54,7 @@ public Design(SourceCodeFile sourceCode, string fieldName, View view) FieldName = fieldName; _designableProperties = new List(LoadDesignableProperties()); + State = new DesignState(this); } public void CreateSubControlDesigns() @@ -182,7 +186,7 @@ private void RegisterCheckboxDesignTimeChanges(CheckBox cb) /// public bool HasKnownColorScheme() { - var userDefinedColorScheme = SelectionManager.Instance.GetOriginalExplicitColorScheme(this) ?? View.GetExplicitColorScheme(); + var userDefinedColorScheme = State.OriginalScheme ?? View.GetExplicitColorScheme(); if (userDefinedColorScheme == null) return false; diff --git a/src/SelectionState.cs b/src/DesignState.cs similarity index 75% rename from src/SelectionState.cs rename to src/DesignState.cs index 4c96e2ab..020a8126 100644 --- a/src/SelectionState.cs +++ b/src/DesignState.cs @@ -3,14 +3,15 @@ namespace TerminalGuiDesigner; /// -/// Describes original +/// Describes state based changes and custom callbacks on a +/// e.g. /// -internal class SelectionState : IDisposable +public class DesignState : IDisposable { public ColorScheme? OriginalScheme { get; set; } public Design Design{ get; } - public SelectionState(Design design) + public DesignState(Design design) { Design = design; OriginalScheme = Design.View.GetExplicitColorScheme(); @@ -27,7 +28,9 @@ private void DrawContentComplete(Rect r) private void DrawBorderlessViewFrame(Rect r) { - var color = SelectionManager.Instance.Selected.Contains(Design) ? + bool isSelected = SelectionManager.Instance.Selected.Contains(Design); + + var color = isSelected ? SelectionManager.Instance.SelectedScheme.Normal : Design.View.ColorScheme.Normal; @@ -40,7 +43,7 @@ private void DrawBorderlessViewFrame(Rect r) { if (y == 0 || y == r.Height - 1 || x == 0 || x == r.Width - 1) { - var rune = (y == r.Height - 1 && x == r.Width - 1) ? '╬' : '.'; + var rune = (y == r.Height - 1 && x == r.Width - 1 && isSelected) ? '╬' : '.'; v.AddRune(x,y,rune); } } @@ -51,7 +54,7 @@ private void DrawBorderlessViewFrame(Rect r) /// public void Dispose() { - Design.View.DrawContentComplete -= DrawContentComplete; + Design.View.DrawContentComplete -= DrawContentComplete; Design.View.ColorScheme = OriginalScheme; } } \ No newline at end of file diff --git a/src/SelectionManager.cs b/src/SelectionManager.cs index cbd6bbaf..fb187206 100644 --- a/src/SelectionManager.cs +++ b/src/SelectionManager.cs @@ -17,8 +17,6 @@ public class SelectionManager /// public IReadOnlyCollection Selected => selection.AsReadOnly(); - Dictionary oldSchemes = new(); - /// /// Set to true to prevent changes to the current /// collection (e.g. if running a modal dialog / context menu). @@ -57,16 +55,8 @@ public ColorScheme SelectedScheme public static SelectionManager Instance = new(); - private ColorScheme selectedScheme; - - - public ColorScheme? GetOriginalExplicitColorScheme(Design design) - { - if (oldSchemes.ContainsKey(design)) - return oldSchemes[design].OriginalScheme; + private ColorScheme? selectedScheme; - return null; - } /// /// Changes the selection without respecting @@ -99,40 +89,25 @@ private void SetSelection(bool respectLock, Design[] designs) foreach (var d in selection) { - // record the old color scheme so we can get reset it - // later when it is no longer selected - oldSchemes.Add(d, new SelectionState(d)); - // since the view is selected mark it so d.View.ColorScheme = SelectedScheme; } } - /// - /// Updates the cached known ColorScheme (prior to selection). Use this method - /// if you are making changes to the ColorScheme of an actively selected object - /// - public void UpdateKnownScheme(Design design, ColorScheme? colorScheme) - { - if(oldSchemes.ContainsKey(design)) - { - oldSchemes[design].OriginalScheme = colorScheme; - } - } - public void Clear(bool respectLock = true) { if (LockSelection && respectLock) return; + var selected = selection.ToArray(); + selection.Clear(); // reset old color schemes so views don't still look selected - foreach (var kvp in oldSchemes) + foreach (var d in selected) { - kvp.Value.Dispose(); + d.View.ColorScheme = d.State.OriginalScheme; } - oldSchemes.Clear(); } /// diff --git a/src/ToCode/ColorSchemeProperty.cs b/src/ToCode/ColorSchemeProperty.cs index d45b379f..66a8eada 100644 --- a/src/ToCode/ColorSchemeProperty.cs +++ b/src/ToCode/ColorSchemeProperty.cs @@ -38,10 +38,7 @@ public override CodeExpression GetRhs() } public override object? GetValue() { - if (SelectionManager.Instance.Selected.Contains(Design)) - return SelectionManager.Instance.GetOriginalExplicitColorScheme(Design); - - return Design.View.GetExplicitColorScheme(); + return Design.State.OriginalScheme ?? Design.View.GetExplicitColorScheme(); } protected override string GetHumanReadableValue() { @@ -62,6 +59,6 @@ public override void SetValue(object? value) { base.SetValue(value); - SelectionManager.Instance.UpdateKnownScheme(Design,value as ColorScheme); + Design.State.OriginalScheme = value as ColorScheme; } } diff --git a/src/ViewExtensions.cs b/src/ViewExtensions.cs index 7a330c18..f1f857eb 100644 --- a/src/ViewExtensions.cs +++ b/src/ViewExtensions.cs @@ -191,6 +191,11 @@ public static bool IsBorderlessContainerView(this View v) if (type == typeof(View) && v.IsBorderless()) return true; + if (v is TabView tabView) + { + return !tabView.Style.ShowBorder || tabView.Style.TabsOnBottom; + } + return false; } diff --git a/tests/ColorSchemeTests.cs b/tests/ColorSchemeTests.cs index 964c0033..2451e8e3 100644 --- a/tests/ColorSchemeTests.cs +++ b/tests/ColorSchemeTests.cs @@ -27,12 +27,16 @@ public void TestHasColorScheme(bool whenMultiSelected) Assert.IsNotNull(d.View.ColorScheme); Assert.IsFalse(d.HasKnownColorScheme()); - d.View.ColorScheme = new ColorScheme(); - + var scheme = new ColorScheme(); + var prop = new SetPropertyOperation(d , d.GetDesignableProperty(nameof(View.ColorScheme)) + ?? throw new Exception("Expected Property did not exist or was not designable"),null,scheme); + + prop.Do(); + // we still don't know about this scheme yet Assert.IsFalse(d.HasKnownColorScheme()); - ColorSchemeManager.Instance.AddOrUpdateScheme("fff",d.View.ColorScheme); + ColorSchemeManager.Instance.AddOrUpdateScheme("fff", scheme); if (whenMultiSelected) { From 60ee03277ae465e17dc477e8babd859209fcc84b Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 13 Oct 2022 12:12:21 +0100 Subject: [PATCH 04/26] Tests for `IsContainerView` and `IsBorderlessContainerView` --- tests/DesignTableViewTests.cs | 2 ++ tests/ViewExtensionsTests.cs | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/tests/DesignTableViewTests.cs b/tests/DesignTableViewTests.cs index a2a532b4..9f7c7a4d 100644 --- a/tests/DesignTableViewTests.cs +++ b/tests/DesignTableViewTests.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using System; using System.IO; using System.Linq; using Terminal.Gui; @@ -9,6 +10,7 @@ namespace tests; + public class TableViewTests : Tests { [Test] diff --git a/tests/ViewExtensionsTests.cs b/tests/ViewExtensionsTests.cs index 2cc5bbe1..030ca6a7 100644 --- a/tests/ViewExtensionsTests.cs +++ b/tests/ViewExtensionsTests.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using System; using Terminal.Gui; using TerminalGuiDesigner; @@ -46,4 +47,30 @@ public void TestHitTest(int x, int y, bool hit, bool border, bool lowerRight) Assert.AreEqual(lowerRight, isLowerRight); Assert.AreEqual(border,isBorder); } + + [TestCase(typeof(Label), false)] + [TestCase(typeof(TableView), false)] + [TestCase(typeof(TabView), true)] + [TestCase(typeof(View), true)] + [TestCase(typeof(Window), true)] + public void TestIsContainerView(Type viewType, bool expectIsContainerView) + { + var inst = (View?)Activator.CreateInstance(viewType) + ?? throw new Exception("CreateInstance returned null!"); + + Assert.AreEqual(expectIsContainerView,inst.IsContainerView()); + } + + [TestCase(typeof(Label), false)] + [TestCase(typeof(TableView), false)] + [TestCase(typeof(TabView), false)] + [TestCase(typeof(Window), false)] + [TestCase(typeof(View), true)] + public void TestOutOfBox_IsBorderlessContainerView(Type viewType, bool expectResult) + { + var inst = (View?)Activator.CreateInstance(viewType) + ?? throw new Exception("CreateInstance returned null!"); + + Assert.AreEqual(expectResult, inst.IsBorderlessContainerView()); + } } From c79c701f850f5d96137fe22caa1eb036dd41c951 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 13 Oct 2022 12:15:41 +0100 Subject: [PATCH 05/26] Tests for TabView IsBorderlessContainerView --- tests/TabViewTests.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/TabViewTests.cs b/tests/TabViewTests.cs index e5a69f69..31f8e55e 100644 --- a/tests/TabViewTests.cs +++ b/tests/TabViewTests.cs @@ -223,4 +223,30 @@ public void TestGetAllDesigns_TabView() Assert.AreEqual(3,tvDesign.GetAllDesigns().Count(),$"Expected only 3 Designs but they were {string.Join(",",tvDesign.GetAllDesigns())}"); } + + [Test] + public void TabView_IsBorderless_DependsOnShowBorder() + { + var inst = new TabView(); + + Assert.IsTrue(inst.Style.ShowBorder); + Assert.False(inst.IsBorderlessContainerView()); + + inst.Style.ShowBorder = false; + + Assert.True(inst.IsBorderlessContainerView()); + } + + [Test] + public void TabView_IsBorderless_DependsOnTabsOnBottom() + { + var inst = new TabView(); + + Assert.IsFalse(inst.Style.TabsOnBottom); + Assert.False(inst.IsBorderlessContainerView()); + + inst.Style.TabsOnBottom = true; + + Assert.True(inst.IsBorderlessContainerView()); + } } \ No newline at end of file From 360011c08e96b4072136318c5208bf6eda80dffc Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 13 Oct 2022 12:23:18 +0100 Subject: [PATCH 06/26] Move Enter callback into DesignState --- src/Design.cs | 1 - src/DesignState.cs | 21 +++++++++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Design.cs b/src/Design.cs index 9c937f54..971a1520 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -156,7 +156,6 @@ public Design CreateSubControlDesign(SourceCodeFile sourceCode, string name, Vie } var d = new Design(sourceCode, name, subView); - subView.Enter += (s => SelectionManager.Instance.SetSelection(d)); return d; } diff --git a/src/DesignState.cs b/src/DesignState.cs index 020a8126..fdce31a2 100644 --- a/src/DesignState.cs +++ b/src/DesignState.cs @@ -6,7 +6,7 @@ namespace TerminalGuiDesigner; /// Describes state based changes and custom callbacks on a /// e.g. /// -public class DesignState : IDisposable +public class DesignState { public ColorScheme? OriginalScheme { get; set; } public Design Design{ get; } @@ -16,9 +16,15 @@ public DesignState(Design design) Design = design; OriginalScheme = Design.View.GetExplicitColorScheme(); Design.View.DrawContentComplete += DrawContentComplete; - } + Design.View.Enter += Enter; + } + + private void Enter(View.FocusEventArgs obj) + { + SelectionManager.Instance.SetSelection(Design); + } - private void DrawContentComplete(Rect r) + private void DrawContentComplete(Rect r) { if(Design.View.IsBorderlessContainerView()) { @@ -48,13 +54,4 @@ private void DrawBorderlessViewFrame(Rect r) } } } - - /// - /// Clears draw callbacks and resets - /// - public void Dispose() - { - Design.View.DrawContentComplete -= DrawContentComplete; - Design.View.ColorScheme = OriginalScheme; - } } \ No newline at end of file From a29240ac261f518f16ad4fc289f07779ae6a918c Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 13 Oct 2022 12:27:44 +0100 Subject: [PATCH 07/26] Update Readme.md to mark that `View` is now usable --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 25b5bfb1..fd9c3c7e 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ The following feature list shows the current capabilities and the roadmap - [x] TextView - [x] TimeField - [x] TreeView - - [ ] View + - [x] View ### Class Diagram ------------------------------- From 49d5d387becc3aaf512078fa709cd5c1c807b6ab Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 17 Oct 2022 13:06:16 +0100 Subject: [PATCH 08/26] Allow designing `View` as a root level class --- README.md | 2 +- src/Design.cs | 17 +++++++++++++++++ src/ToCode/ViewToCode.cs | 11 +++++++---- src/UI/Editor.cs | 2 +- src/ViewExtensions.cs | 14 ++++++++++++++ 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fd9c3c7e..c0afc1bc 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ The following feature list shows the current capabilities and the roadmap - [x] Design classes - [x] Window - [x] Dialog - - [ ] View + - [x] View - [ ] Top level (with statusbar and or menu) - [x] Configure root properties (e.g. Window.Width, Title etc) - [x] Configure subview properties diff --git a/src/Design.cs b/src/Design.cs index 971a1520..0f400a0a 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -59,6 +59,23 @@ public Design(SourceCodeFile sourceCode, string fieldName, View view) public void CreateSubControlDesigns() { + // if users Type (e.g. MyView) inherits directly from View then we need to do some work + if (View.GetType().BaseType == typeof(View)) + { + // Views alone do not have any explicit colors because they are designed + // to inherit from parent (e.g. Window/Dialog). When creating a new view + // we need to use the base one (as if it was already mounted on a Window) + View.ColorScheme = Colors.Base; + + // The view base class does not fill over its old stale contents ever + View.DrawContent += (r) => + { + // manually erase stale content + Application.Driver.SetAttribute(View.ColorScheme.Normal); + Application.Driver.DrawFrame(View.ViewToScreen(r), 0, true); + }; + } + CreateSubControlDesigns(View); } diff --git a/src/ToCode/ViewToCode.cs b/src/ToCode/ViewToCode.cs index 5ab08a75..370053c9 100644 --- a/src/ToCode/ViewToCode.cs +++ b/src/ToCode/ViewToCode.cs @@ -31,22 +31,25 @@ public Design GenerateNewView(FileInfo csFilePath, string namespaceName,Type vie var csharpCode = GetGenerateNewViewCode(className, namespaceName); File.WriteAllText(sourceFile.CsFile.FullName, csharpCode); - var view = (View)(Activator.CreateInstance(viewType) ?? throw new Exception($"Could not create instance of Type '{viewType}' ('Activator.CreateInstance' returned null)")); + var prototype = (View)(Activator.CreateInstance(viewType) ?? throw new Exception($"Could not create instance of Type '{viewType}' ('Activator.CreateInstance' returned null)")); // Unlike Window and Dialog the default constructor on // View will be a size 0 view. Make it big so it can be // edited if(viewType == typeof(View)) { - view.Width = Dim.Fill(); - view.Height = Dim.Fill(); + prototype.Width = Dim.Fill(); + prototype.Height = Dim.Fill(); } - var design = new Design(sourceFile, Design.RootDesignName, view); + // use the prototype to create a designer cs file + var design = new Design(sourceFile, Design.RootDesignName, prototype); design.CreateSubControlDesigns(); GenerateDesignerCs(design, sourceFile,viewType); + // Reload the designer cs file to create a new instance (which is returned). + // NOTE: prototype is not the same instance that is returned; var decompiler = new CodeToView(sourceFile); return decompiler.CreateInstance(); diff --git a/src/UI/Editor.cs b/src/UI/Editor.cs index 777e6094..295bafc2 100644 --- a/src/UI/Editor.cs +++ b/src/UI/Editor.cs @@ -847,7 +847,7 @@ private void New() private Type[] GetSupportedRootViews() { - return new Type[] { typeof(Window), typeof(Dialog) }; + return new Type[] { typeof(Window), typeof(Dialog), typeof(View) }; } private void New(FileInfo toOpen, Type typeToCreate, string? explicitNamespace) diff --git a/src/ViewExtensions.cs b/src/ViewExtensions.cs index f1f857eb..40407fb4 100644 --- a/src/ViewExtensions.cs +++ b/src/ViewExtensions.cs @@ -148,6 +148,20 @@ public static string GetActualText(this View view) } + /// + /// Converts the view-relative into a screen relative rectangle + /// + /// + /// + /// + /// + public static Rect ViewToScreen(this View v, Rect rect, bool clipped = true) + { + v.ViewToScreen(rect.X, rect.Y, out var x1, out var y1, clipped); + v.ViewToScreen(rect.Width, rect.Height, out var x2, out var y2, clipped); + + return new Rect(x1, y1, x2 - x1, y2 - y1); + } /// /// Converts a view-relative (col,row) position to a screen-relative position (col,row). The values are optionally clamped to the screen dimensions. /// From 1f36b3dbbc3c017770df85768b8ec9bf5227eb37 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 17 Oct 2022 13:09:39 +0100 Subject: [PATCH 09/26] Make TopLevel a designable root Type --- README.md | 2 +- src/UI/Editor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c0afc1bc..7c2934ab 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ The following feature list shows the current capabilities and the roadmap - [x] Window - [x] Dialog - [x] View - - [ ] Top level (with statusbar and or menu) + - [x] Top level (with statusbar and or menu) - [x] Configure root properties (e.g. Window.Width, Title etc) - [x] Configure subview properties - [x] (Name) diff --git a/src/UI/Editor.cs b/src/UI/Editor.cs index 295bafc2..58cdc5f8 100644 --- a/src/UI/Editor.cs +++ b/src/UI/Editor.cs @@ -847,7 +847,7 @@ private void New() private Type[] GetSupportedRootViews() { - return new Type[] { typeof(Window), typeof(Dialog), typeof(View) }; + return new Type[] { typeof(Window), typeof(Dialog), typeof(View) , typeof(Toplevel)}; } private void New(FileInfo toOpen, Type typeToCreate, string? explicitNamespace) From 47ee5ffc4618ba35f14e5e0cfb2f98b2c208c656 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 17 Oct 2022 13:12:29 +0100 Subject: [PATCH 10/26] Avoid clearing custom ColorScheme when opening a View --- src/Design.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Design.cs b/src/Design.cs index 0f400a0a..c11cadc2 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -65,7 +65,10 @@ public void CreateSubControlDesigns() // Views alone do not have any explicit colors because they are designed // to inherit from parent (e.g. Window/Dialog). When creating a new view // we need to use the base one (as if it was already mounted on a Window) - View.ColorScheme = Colors.Base; + if(View.ColorScheme == null) + { + View.ColorScheme = Colors.Base; + } // The view base class does not fill over its old stale contents ever View.DrawContent += (r) => From 3e6b41e9aa9eae31170a7fc82d0bd2b8a3dd56f9 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 17 Oct 2022 13:50:55 +0100 Subject: [PATCH 11/26] Switch to using `Clear` instead of `DrawFrame` for invalidating View backgrounds --- src/Design.cs | 2 +- src/ViewExtensions.cs | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Design.cs b/src/Design.cs index c11cadc2..a7cbc435 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -75,7 +75,7 @@ public void CreateSubControlDesigns() { // manually erase stale content Application.Driver.SetAttribute(View.ColorScheme.Normal); - Application.Driver.DrawFrame(View.ViewToScreen(r), 0, true); + View.Clear(); }; } diff --git a/src/ViewExtensions.cs b/src/ViewExtensions.cs index 40407fb4..d4f06522 100644 --- a/src/ViewExtensions.cs +++ b/src/ViewExtensions.cs @@ -147,21 +147,6 @@ public static string GetActualText(this View view) return GetNearestContainerDesign(d.View.SuperView); } - - /// - /// Converts the view-relative into a screen relative rectangle - /// - /// - /// - /// - /// - public static Rect ViewToScreen(this View v, Rect rect, bool clipped = true) - { - v.ViewToScreen(rect.X, rect.Y, out var x1, out var y1, clipped); - v.ViewToScreen(rect.Width, rect.Height, out var x2, out var y2, clipped); - - return new Rect(x1, y1, x2 - x1, y2 - y1); - } /// /// Converts a view-relative (col,row) position to a screen-relative position (col,row). The values are optionally clamped to the screen dimensions. /// From 3f98f3ceff57040c4551accbc4f649313906d6f6 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 17 Oct 2022 14:11:09 +0100 Subject: [PATCH 12/26] Give TopLevel and View both the default color scheme --- src/Design.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Design.cs b/src/Design.cs index a7cbc435..6207181d 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -59,18 +59,22 @@ public Design(SourceCodeFile sourceCode, string fieldName, View view) public void CreateSubControlDesigns() { - // if users Type (e.g. MyView) inherits directly from View then we need to do some work - if (View.GetType().BaseType == typeof(View)) + + // Unlike Window/Dialog the View/TopLevel classes do not have an explicit + // colors schemes. When creating a new View or TopLevel we need to use + // the Colors.Base and fiddle a bit with coloring/clearing to ensure things render correctly + + var baseType = View.GetType().BaseType; + + if (baseType == typeof(View) || baseType == typeof(Toplevel)) { - // Views alone do not have any explicit colors because they are designed - // to inherit from parent (e.g. Window/Dialog). When creating a new view - // we need to use the base one (as if it was already mounted on a Window) - if(View.ColorScheme == null) + if(View.ColorScheme == null || View.ColorScheme == Colors.TopLevel) { View.ColorScheme = Colors.Base; } - // The view base class does not fill over its old stale contents ever + // View and TopLevel doe not clear their states regularly during drawing + // we have to do that ourselves View.DrawContent += (r) => { // manually erase stale content From fafd8199a0bb57fcae5f5ca6839b5f9ebf413e66 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 17 Oct 2022 14:20:39 +0100 Subject: [PATCH 13/26] Add shortcut for toggling borders on/off (Ctrl+B) --- src/DesignState.cs | 3 ++- src/Keys.yaml | 1 + src/UI/Editor.cs | 10 ++++++++++ src/UI/KeyMap.cs | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/DesignState.cs b/src/DesignState.cs index fdce31a2..64da3bd5 100644 --- a/src/DesignState.cs +++ b/src/DesignState.cs @@ -1,4 +1,5 @@ using Terminal.Gui; +using TerminalGuiDesigner.UI; namespace TerminalGuiDesigner; @@ -26,7 +27,7 @@ private void Enter(View.FocusEventArgs obj) private void DrawContentComplete(Rect r) { - if(Design.View.IsBorderlessContainerView()) + if(Design.View.IsBorderlessContainerView() && Editor.ShowBorders) { DrawBorderlessViewFrame(r); } diff --git a/src/Keys.yaml b/src/Keys.yaml index 1ab7bdd8..899802a2 100644 --- a/src/Keys.yaml +++ b/src/Keys.yaml @@ -12,6 +12,7 @@ Delete: DeleteChar ToggleDragging: F3 AddView: F2 ToggleShowFocused: L, CtrlMask +ToggleShowBorders: B, CtrlMask RightClick: Button3Clicked Copy: C, CtrlMask Paste: V, CtrlMask diff --git a/src/UI/Editor.cs b/src/UI/Editor.cs index 58cdc5f8..e0dbfc31 100644 --- a/src/UI/Editor.cs +++ b/src/UI/Editor.cs @@ -14,6 +14,7 @@ public class Editor : Toplevel private SourceCodeFile? _currentDesignerFile; private bool enableDrag = true; private bool enableShowFocused = true; + public static bool ShowBorders = true; bool _editting = false; @@ -37,6 +38,7 @@ public class Editor : Toplevel /// private Guid? _lastSavedOperation; + private string GetHelpWithEmptyFormLoaded() { return @$"{_keyMap.AddView} to Add a View"; @@ -54,6 +56,7 @@ private string GetHelp() {_keyMap.ShowColorSchemes} - Color Schemes {_keyMap.ToggleDragging} - Toggle mouse dragging on/off {_keyMap.ToggleShowFocused} - Toggle show focused view field name +{_keyMap.ToggleShowBorders} - Toggle dotted borders for frameless views {_keyMap.EditProperties} - Edit View Properties {_keyMap.ViewSpecificOperations} - View Specific Operations {_keyMap.EditRootProperties} - Edit Root Properties @@ -552,6 +555,13 @@ public bool HandleKey(KeyEvent keyEvent) SetNeedsDisplay(); return true; } + if (keyEvent.Key == _keyMap.ToggleShowBorders) + { + ShowBorders = !ShowBorders; + SetNeedsDisplay(); + return true; + } + if (keyEvent.Key == _keyMap.SelectAll) { SelectAll(); diff --git a/src/UI/KeyMap.cs b/src/UI/KeyMap.cs index 970f652a..a23d708f 100644 --- a/src/UI/KeyMap.cs +++ b/src/UI/KeyMap.cs @@ -19,6 +19,7 @@ public class KeyMap public Key ToggleDragging { get; set; } = Key.F3; public Key AddView { get; set; } = Key.F2; public Key ToggleShowFocused { get; set; } = Key.CtrlMask | Key.L; + public Key ToggleShowBorders { get; set; } = Key.CtrlMask | Key.B; public MouseFlags RightClick { get; set; } = MouseFlags.Button3Clicked; public Key Copy { get; set; } = Key.CtrlMask | Key.C; public Key Paste { get; set; } = Key.CtrlMask | Key.V; From ee9eb2ba2c46097f523f0f0a362ddfcf8b6fbd1f Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 17 Oct 2022 14:42:38 +0100 Subject: [PATCH 14/26] Fix loosing color scheme during selection events for View and TopLevel classes --- src/Design.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Design.cs b/src/Design.cs index 6207181d..b6ddbee0 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -70,7 +70,7 @@ public void CreateSubControlDesigns() { if(View.ColorScheme == null || View.ColorScheme == Colors.TopLevel) { - View.ColorScheme = Colors.Base; + State.OriginalScheme = View.ColorScheme = Colors.Base; } // View and TopLevel doe not clear their states regularly during drawing From 7a01bc894e1d58f0c50595cef861330128f964ea Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 18 Oct 2022 08:14:34 +0100 Subject: [PATCH 15/26] Change border so it does not draw over content --- src/DesignState.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DesignState.cs b/src/DesignState.cs index 64da3bd5..978ff670 100644 --- a/src/DesignState.cs +++ b/src/DesignState.cs @@ -16,7 +16,7 @@ public DesignState(Design design) { Design = design; OriginalScheme = Design.View.GetExplicitColorScheme(); - Design.View.DrawContentComplete += DrawContentComplete; + Design.View.DrawContent += DrawContent; Design.View.Enter += Enter; } @@ -25,7 +25,7 @@ private void Enter(View.FocusEventArgs obj) SelectionManager.Instance.SetSelection(Design); } - private void DrawContentComplete(Rect r) + private void DrawContent(Rect r) { if(Design.View.IsBorderlessContainerView() && Editor.ShowBorders) { From 5d8452ed606c35dba31efb9e01503ad706ce3ec5 Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 18 Oct 2022 08:19:55 +0100 Subject: [PATCH 16/26] Fix not being able to drag out of subcontainers into root when root is View or TopLevel --- src/ViewExtensions.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ViewExtensions.cs b/src/ViewExtensions.cs index d4f06522..8255250f 100644 --- a/src/ViewExtensions.cs +++ b/src/ViewExtensions.cs @@ -179,6 +179,17 @@ public static void ViewToScreen(this View v, int col, int row, out int rcol, out public static bool IsContainerView(this View v) { var type = v.GetType(); + + if(v.Data is Design d) + { + // The root class user is designing (e.g. MyView) could be inheriting from + // TopLevel or View in which case we must allow dropping into it + if (d.IsRoot) + { + return true; + } + } + // TODO: are there any others? return v is TabView || v is FrameView || v is Window || type == typeof(View) || type.Name.Equals("ContentView"); } From dba1c75b557816363855df2de9848233ba957d57 Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 18 Oct 2022 08:23:30 +0100 Subject: [PATCH 17/26] Add note to remove DrawContent hack when https://github.com/gui-cs/Terminal.Gui/issues/2094 is resolved --- src/Design.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Design.cs b/src/Design.cs index b6ddbee0..778f1aa7 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -73,12 +73,14 @@ public void CreateSubControlDesigns() State.OriginalScheme = View.ColorScheme = Colors.Base; } + // TODO: Remove this when https://github.com/gui-cs/Terminal.Gui/issues/2094 is fixed + // HACK + // View and TopLevel doe not clear their states regularly during drawing // we have to do that ourselves View.DrawContent += (r) => { // manually erase stale content - Application.Driver.SetAttribute(View.ColorScheme.Normal); View.Clear(); }; } From 2fdb77694dca575af03d0619e5fd2cb2e47c168f Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 18 Oct 2022 13:40:56 +0100 Subject: [PATCH 18/26] Set color before clearing --- src/Design.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Design.cs b/src/Design.cs index 778f1aa7..37af306a 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -81,6 +81,7 @@ public void CreateSubControlDesigns() View.DrawContent += (r) => { // manually erase stale content + Application.Driver.SetAttribute(View.ColorScheme.Normal); View.Clear(); }; } From 87310b63d8a5b05b9c2f87347952d14c8f093a78 Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 18 Oct 2022 15:58:11 +0100 Subject: [PATCH 19/26] Suppress native context menus better and fix `Clear` when original color scheme is null --- src/Design.cs | 18 ++++++++++++++++-- src/UI/Editor.cs | 7 +++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Design.cs b/src/Design.cs index 37af306a..1153d594 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -81,7 +81,9 @@ public void CreateSubControlDesigns() View.DrawContent += (r) => { // manually erase stale content - Application.Driver.SetAttribute(View.ColorScheme.Normal); + Application.Driver.SetAttribute( + State.OriginalScheme?.Normal ?? + View.ColorScheme.Normal); View.Clear(); }; } @@ -156,10 +158,16 @@ public Design CreateSubControlDesign(SourceCodeFile sourceCode, string name, Vie if (subView is TextView txt) { // prevent control from responding to events - txt.MouseClick += (s) => s.Handled = true; + txt.MouseClick += SuppressNativeClickEvents; txt.KeyDown += (s) => s.Handled = true; } + if (subView is TextField tf) + { + // prevent control from responding to events + tf.MouseClick += SuppressNativeClickEvents; + tf.KeyDown += (s) => s.Handled = true; + } if (subView is TreeView tree) { tree.AddObject(new TreeNode("Example Branch 1") @@ -186,6 +194,12 @@ public Design CreateSubControlDesign(SourceCodeFile sourceCode, string name, Vie return d; } + private void SuppressNativeClickEvents(View.MouseEventArgs obj) + { + // Suppress everything except single click (selection) + obj.Handled = obj.MouseEvent.Flags != MouseFlags.Button1Clicked; + } + private void RegisterCheckboxDesignTimeChanges(CheckBox cb) { // prevent space toggling the checkbox diff --git a/src/UI/Editor.cs b/src/UI/Editor.cs index e0dbfc31..904c135f 100644 --- a/src/UI/Editor.cs +++ b/src/UI/Editor.cs @@ -238,13 +238,13 @@ public void Run(Options options) } }; + Application.RootMouseEvent += (m) => { - // if another window is showing don't respond to mouse if (!IsCurrentTop) return; - + if (_editting || !enableDrag || _viewBeingEdited == null) return; @@ -262,7 +262,10 @@ public void Run(Options options) { var d = hit.GetNearestDesign() ?? _viewBeingEdited; if (d != null) + { CreateAndShowContextMenu(m, d); + } + } } } From fe5948dbe15f190eb43abc9f9446055bd0190614 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 28 Oct 2022 22:41:16 +0100 Subject: [PATCH 20/26] Remove bad merge rename fix --- tests/DesignTableViewTests.cs | 67 ----------------------------------- 1 file changed, 67 deletions(-) delete mode 100644 tests/DesignTableViewTests.cs diff --git a/tests/DesignTableViewTests.cs b/tests/DesignTableViewTests.cs deleted file mode 100644 index 9f7c7a4d..00000000 --- a/tests/DesignTableViewTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -using NUnit.Framework; -using System; -using System.IO; -using System.Linq; -using Terminal.Gui; -using TerminalGuiDesigner; -using TerminalGuiDesigner.FromCode; -using TerminalGuiDesigner.Operations; -using TerminalGuiDesigner.ToCode; - -namespace tests; - - -public class TableViewTests : Tests -{ - [Test] - public void TestRoundTrip_PreserveColumns() - { - var viewToCode = new ViewToCode(); - - var file = new FileInfo("TestRoundTrip_PreserveColumns.cs"); - var designOut = viewToCode.GenerateNewView(file, "YourNamespace",typeof(Window), out var sourceCode); - - var factory = new ViewFactory(); - var tvOut = factory.Create(typeof(TableView)); - - OperationManager.Instance.Do(new AddViewOperation(sourceCode, tvOut, designOut, "myTable")); - - viewToCode.GenerateDesignerCs(designOut, sourceCode,typeof(Window)); - - var tableOut = designOut.View.GetActualSubviews().OfType().Single(); - - var codeToView = new CodeToView(sourceCode); - var designBackIn = codeToView.CreateInstance(); - - var tableIn = designBackIn.View.GetActualSubviews().OfType().Single(); - - Assert.IsNotNull(tableIn.Table); - - Assert.AreEqual(tableOut.Table.Columns.Count, tableIn.Table.Columns.Count); - Assert.AreEqual(tableOut.Table.Rows.Count, tableIn.Table.Rows.Count); - } - - [Test] - public void TestRoundTrip_TwoTablesWithDuplicatedColumns() - { - var viewToCode = new ViewToCode(); - - var file = new FileInfo("TestRoundTrip_TwoTablesWithDuplicatedColumns.cs"); - var designOut = viewToCode.GenerateNewView(file, "YourNamespace",typeof(Window), out var sourceCode); - - var factory = new ViewFactory(); - var tvOut1 = factory.Create(typeof(TableView)); - var tvOut2 = factory.Create(typeof(TableView)); - - // The column names exactly match - OperationManager.Instance.Do(new AddViewOperation(sourceCode, tvOut1, designOut, "myTable1")); - OperationManager.Instance.Do(new AddViewOperation(sourceCode, tvOut2, designOut, "myTable2")); - - viewToCode.GenerateDesignerCs(designOut, sourceCode,typeof(Window)); - - var codeToView = new CodeToView(sourceCode); - var designBackIn = codeToView.CreateInstance(); - - Assert.AreEqual(2,designBackIn.View.GetActualSubviews().OfType().Count()); - } -} From 1fec7347282fb86f9ccdde36fd74aa747d6c01cb Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 28 Oct 2022 22:48:37 +0100 Subject: [PATCH 21/26] Fix TestColorScheme_RoundTrip --- tests/ColorSchemeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ColorSchemeTests.cs b/tests/ColorSchemeTests.cs index 31cd151e..982cc5cb 100644 --- a/tests/ColorSchemeTests.cs +++ b/tests/ColorSchemeTests.cs @@ -189,7 +189,7 @@ public void TestColorScheme_RoundTrip(bool multiSelectBeforeSaving) { //unselect it so it is rendered with correct scheme SelectionManager.Instance.Clear(); - l.ColorScheme = mgr.Schemes.Single().Scheme; + l.ColorScheme = d.State.OriginalScheme = mgr.Schemes.Single().Scheme; if (multiSelectBeforeSaving) { From dcc3173eddf737d852e767520314821b98fdeb01 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 28 Oct 2022 22:55:56 +0100 Subject: [PATCH 22/26] Added --experimental flag to CLI --- src/Options.cs | 5 ++++- src/Program.cs | 1 + src/UI/Editor.cs | 12 +++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Options.cs b/src/Options.cs index b0901b0b..9a111d52 100644 --- a/src/Options.cs +++ b/src/Options.cs @@ -21,7 +21,10 @@ public class Options [Option( HelpText = "Enables UseSystemConsole, an alternative console display driver")] public bool Usc { get;set; } - + + [Option( HelpText = "Enables experimental features")] + public bool Experimental { get;set; } + #nullable enable warnings [Usage(ApplicationAlias = "TerminalGuiDesigner")] diff --git a/src/Program.cs b/src/Program.cs index 94e15a1b..e54b1db5 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -14,6 +14,7 @@ public static void Main(string[] args) .WithParsed(o => { Application.UseSystemConsole = o.Usc; + Editor.Experimental = o.Experimental; Application.Init(); var editor = new Editor(); diff --git a/src/UI/Editor.cs b/src/UI/Editor.cs index 904c135f..d9cff379 100644 --- a/src/UI/Editor.cs +++ b/src/UI/Editor.cs @@ -38,6 +38,10 @@ public class Editor : Toplevel /// private Guid? _lastSavedOperation; + /// + /// True to enable experimental features + /// + public static bool Experimental { get; internal set; } private string GetHelpWithEmptyFormLoaded() { @@ -860,7 +864,13 @@ private void New() private Type[] GetSupportedRootViews() { - return new Type[] { typeof(Window), typeof(Dialog), typeof(View) , typeof(Toplevel)}; + // TODO: When more robust, remove these from experimental status + if(Editor.Experimental) + { + return new Type[] { typeof(Window), typeof(Dialog), typeof(View) , typeof(Toplevel)}; + } + + return new Type[] { typeof(Window), typeof(Dialog)}; } private void New(FileInfo toOpen, Type typeToCreate, string? explicitNamespace) From f3bb325084d6930ab64759fe532a5f960ae1f37b Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 29 Oct 2022 09:31:57 +0100 Subject: [PATCH 23/26] Mouse drag fixes and improvements --- src/Operations/DragOperation.cs | 6 +++--- src/UI/MouseManager.cs | 4 ++-- src/ViewExtensions.cs | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Operations/DragOperation.cs b/src/Operations/DragOperation.cs index 9087fc28..a34369ca 100644 --- a/src/Operations/DragOperation.cs +++ b/src/Operations/DragOperation.cs @@ -16,14 +16,14 @@ private class DragMemento public Pos OriginalX {get;} public Pos OriginalY {get;} - public View OriginalSuperView {get;} + public View? OriginalSuperView {get;} public DragMemento(Design design) { Design = design; OriginalX = design.View.X; OriginalY = design.View.Y; - OriginalSuperView = design.View.SuperView; + OriginalSuperView = design.View.SuperView?.GetNearestDesign()?.View; } } @@ -108,7 +108,7 @@ private bool Do(DragMemento mem) // if changing to a new container if (DropInto != mem.OriginalSuperView && mem.OriginalSuperView != null) { - mem.OriginalSuperView.Remove(mem.Design.View); + mem.Design.View.SuperView.Remove(mem.Design.View); DropInto.Add(mem.Design.View); } } diff --git a/src/UI/MouseManager.cs b/src/UI/MouseManager.cs index c8b906fd..8b2bd6dc 100644 --- a/src/UI/MouseManager.cs +++ b/src/UI/MouseManager.cs @@ -114,10 +114,10 @@ public void HandleMouse(MouseEvent m, Design viewBeingEdited) if (SelectionStart != null && SelectionBox != null && SelectionContainer != null) { SelectionManager.Instance.SetSelection( - SelectionContainer.Subviews + SelectionContainer.GetActualSubviews() .Where(v => v.IntersectsScreenRect(SelectionBox.Value)) .Select(v=>v.GetNearestDesign()) - .Where(d=>d !=null) + .Where(d=>d !=null && !d.IsRoot) .Cast() .ToArray() ); diff --git a/src/ViewExtensions.cs b/src/ViewExtensions.cs index 3feacdcc..d1eff0e4 100644 --- a/src/ViewExtensions.cs +++ b/src/ViewExtensions.cs @@ -203,7 +203,7 @@ public static bool IsBorderlessContainerView(this View v) // TODO: are there any others? if (type == typeof(View) && v.IsBorderless()) return true; - + if (v is TabView tabView) { return !tabView.Style.ShowBorder || tabView.Style.TabsOnBottom; @@ -237,6 +237,9 @@ public static bool IsBorderless(this View v) int resizeBoxArea = 2; + // get nearest Design to the clicked thing (avoids issues with ContentView and other wierd subviews etc) + hit = hit?.GetNearestDesign()?.View; + if (hit != null) { hit.ViewToScreen(hit.Bounds.Right, hit.Bounds.Bottom, out int lowerRightX, out int lowerRightY, true); From 6bcb51ae1f875091c2a5079a1e1285e6fcc13bdd Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 29 Oct 2022 09:32:20 +0100 Subject: [PATCH 24/26] Allow -e for experimental and document in README.md --- README.md | 7 ++++--- src/Options.cs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7c2934ab..8e8d5c26 100644 --- a/README.md +++ b/README.md @@ -112,13 +112,14 @@ You can create a menu separator by typing `---` ### Features ------------------------------- -The following feature list shows the current capabilities and the roadmap +The following feature list shows the current capabilities and the roadmap. Features in +italics are experimental and require passing the `-e` flag when starting application. - [x] Design classes - [x] Window - [x] Dialog - - [x] View - - [x] Top level (with statusbar and or menu) + - [x] _View_ + - [x] _Top level_ - [x] Configure root properties (e.g. Window.Width, Title etc) - [x] Configure subview properties - [x] (Name) diff --git a/src/Options.cs b/src/Options.cs index 9a111d52..08c51a55 100644 --- a/src/Options.cs +++ b/src/Options.cs @@ -22,7 +22,7 @@ public class Options [Option( HelpText = "Enables UseSystemConsole, an alternative console display driver")] public bool Usc { get;set; } - [Option( HelpText = "Enables experimental features")] + [Option( 'e', HelpText = "Enables experimental features")] public bool Experimental { get;set; } #nullable enable warnings From 7db99428a6317619060424b877080340510ce514 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 29 Oct 2022 09:32:44 +0100 Subject: [PATCH 25/26] ViewFactory no longer creates TextField with Text property set (now blank) --- src/ViewFactory.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ViewFactory.cs b/src/ViewFactory.cs index d30c770b..571846ee 100644 --- a/src/ViewFactory.cs +++ b/src/ViewFactory.cs @@ -70,7 +70,14 @@ public View Create(Type t) Height = 5, }; } - + if (t == typeof(TextField)) + { + return new TextField + { + Width = 10, + Height = 1, + }; + } if (typeof(GraphView).IsAssignableFrom(t)) { return new GraphView From 4fa46b904379cf0d3e8c15b08b8d323dae0a068c Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 29 Oct 2022 09:48:38 +0100 Subject: [PATCH 26/26] Fix unit tests to have Designs --- tests/DragOperationTests.cs | 3 +++ tests/ViewExtensionsTests.cs | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/DragOperationTests.cs b/tests/DragOperationTests.cs index 1c79dac6..83879400 100644 --- a/tests/DragOperationTests.cs +++ b/tests/DragOperationTests.cs @@ -94,12 +94,15 @@ public void TestSimpleDrag_IntoAnotherView() Width = 5, Height = 4 }; + container1.Data = new Design(d.SourceCode, "v1", container1); + var container2 = new View{ X=5, Y=6, Width = 5, Height = 4 }; + container2.Data = new Design(d.SourceCode, "v2", container2); d.View.Add(container1); d.View.Add(container2); diff --git a/tests/ViewExtensionsTests.cs b/tests/ViewExtensionsTests.cs index 030ca6a7..a4e8184c 100644 --- a/tests/ViewExtensionsTests.cs +++ b/tests/ViewExtensionsTests.cs @@ -23,12 +23,12 @@ public class ViewExtensionsTests : Tests [TestCase(3,4,true,false,false)] public void TestHitTest(int x, int y, bool hit, bool border, bool lowerRight) { - var v = new View{ - X=2, - Y=3, - Width = 5, - Height = 3 - }; + var v = Get10By10View().View; + + v.X = 2; + v.Y = 3; + v.Width = 5; + v.Height = 3; Application.Top.Add(v); bool isLowerRight;