From 378f7c34e002c0fb7417bf7ef8bf8b96eb1ecc84 Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Mon, 6 Sep 2021 08:35:34 -0700 Subject: [PATCH] lots of fun progress on sheet editing --- .../DesignTools/Primitives/TextObject3D.cs | 4 + .../DesignTools/Sheets/SheetData.cs | 84 ++++-- .../View3D/Actions/SheetEditor.cs | 108 ++++--- .../View3D/Actions/SheetFieldWidget.cs | 284 ++++++++++++++++++ 4 files changed, 413 insertions(+), 67 deletions(-) create mode 100644 MatterControlLib/PartPreviewWindow/View3D/Actions/SheetFieldWidget.cs diff --git a/MatterControlLib/DesignTools/Primitives/TextObject3D.cs b/MatterControlLib/DesignTools/Primitives/TextObject3D.cs index 00c92f703..0cb6ebf0b 100644 --- a/MatterControlLib/DesignTools/Primitives/TextObject3D.cs +++ b/MatterControlLib/DesignTools/Primitives/TextObject3D.cs @@ -125,6 +125,10 @@ namespace MatterHackers.MatterControl.DesignTools { await Rebuild(); } + else if (SheetObject3D.NeedsRebuild(this, invalidateArgs)) + { + await Rebuild(); + } else { base.OnInvalidate(invalidateArgs); diff --git a/MatterControlLib/DesignTools/Sheets/SheetData.cs b/MatterControlLib/DesignTools/Sheets/SheetData.cs index 255e97239..8c75792ef 100644 --- a/MatterControlLib/DesignTools/Sheets/SheetData.cs +++ b/MatterControlLib/DesignTools/Sheets/SheetData.cs @@ -60,7 +60,7 @@ namespace MatterHackers.MatterControl.DesignTools { if (!tabelCalculated) { - Recalculate(); + BuildTableConstants(); } if(expression.StartsWith("=")) @@ -243,43 +243,79 @@ namespace MatterHackers.MatterControl.DesignTools public void Recalculate() { + BuildTableConstants(); + + Recalculated?.Invoke(this, null); + } + + private void BuildTableConstants() + { + double GetValue((int x, int y, TableCell cell) xyCell) + { + var expression = xyCell.cell.Expression; + if (expression.StartsWith("=")) + { + expression = expression.Substring(1); + } + if (expression.StartsWith(".")) + { + expression = "0" + expression; + } + var evaluator = new Expression(expression.ToLower()); + AddConstants(evaluator); + var value = evaluator.calculate(); + return value; + } + lock (locker) { constants.Clear(); - // WIP: sort the cell by reference (needs to be DAG) - var list = EnumerateCells().OrderByDescending(i => i.cell.Expression).ToList(); - foreach (var xyc in list) + var list = EnumerateCells().ToList(); + var noSolution = new List<(int, int, TableCell)>(); + var addedConstant = false; + + // process all the cells until we can no longer solve a new value (needs to be DAG) + do { - var expression = xyc.cell.Expression; - if (expression.StartsWith("=")) + addedConstant = false; + foreach (var xyCell in list) { - expression = expression.Substring(1); - } - if (expression.StartsWith(".")) - { - expression = "0" + expression; - } - var evaluator = new Expression(expression.ToLower()); - AddConstants(evaluator); - var value = evaluator.calculate(); - if (double.IsNaN(value) - || double.IsInfinity(value)) - { - value = 0; + double value = GetValue(xyCell); + if (double.IsNaN(value) + || double.IsInfinity(value)) + { + noSolution.Add(xyCell); + } + else + { + constants.Add(CellId(xyCell.x, xyCell.y).ToLower(), value); + if (!string.IsNullOrEmpty(xyCell.cell.Name)) + { + constants.Add(xyCell.cell.Name.ToLower(), value); + } + + addedConstant = true; + } } - constants.Add(CellId(xyc.x, xyc.y).ToLower(), value); - if (!string.IsNullOrEmpty(xyc.cell.Name)) + list.Clear(); + list.AddRange(noSolution); + noSolution.Clear(); + } while (list.Count > 0 && addedConstant); + + // add the rest of the cells to constants as 0's + foreach (var xyCell in list) + { + constants.Add(CellId(xyCell.x, xyCell.y).ToLower(), 0); + if (!string.IsNullOrEmpty(xyCell.cell.Name)) { - constants.Add(xyc.cell.Name.ToLower(), value); + constants.Add(xyCell.cell.Name.ToLower(), 0); } } tabelCalculated = true; } - - Recalculated?.Invoke(this, null); } private void AddConstants(Expression evaluator) diff --git a/MatterControlLib/PartPreviewWindow/View3D/Actions/SheetEditor.cs b/MatterControlLib/PartPreviewWindow/View3D/Actions/SheetEditor.cs index 20d284b68..247198ed1 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/Actions/SheetEditor.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/Actions/SheetEditor.cs @@ -34,17 +34,20 @@ using MatterHackers.Agg.UI; using MatterHackers.MatterControl.PartPreviewWindow; using MatterHackers.DataConverters3D; using MatterHackers.Localizations; +using System.Linq; +using System.Diagnostics; namespace MatterHackers.MatterControl.DesignTools { public class SheetEditorWidget : FlowLayoutWidget { - private SheetData sheetData; + public SheetData SheetData { get; private set; } Point2D selectedCell = new Point2D(-1, -1); Dictionary<(int, int), GuiWidget> CellWidgetsByLocation = new Dictionary<(int, int), GuiWidget>(); private ThemeConfig theme; private MHTextEditWidget editSelectedName; - private MHTextEditWidget editSelectedExpression; + public MHTextEditWidget EditSelectedExpression { get; private set; } + private GridWidget gridWidget; public SheetEditorWidget(SheetData sheetData, UndoBuffer undoBuffer, ThemeConfig theme) : base(FlowDirection.TopToBottom) @@ -52,9 +55,9 @@ namespace MatterHackers.MatterControl.DesignTools this.theme = theme; HAnchor = HAnchor.MaxFitOrStretch; - this.sheetData = sheetData; + this.SheetData = sheetData; var countWidth = 10 * GuiWidget.DeviceScale; - var cellWidth = 50 * GuiWidget.DeviceScale; + var cellEditNameWidth = 80 * GuiWidget.DeviceScale; // put in the edit row var editSelectionGroup = this.AddChild(new FlowLayoutWidget() @@ -63,73 +66,65 @@ namespace MatterHackers.MatterControl.DesignTools VAnchor = VAnchor.Fit, }); - editSelectedName = new MHTextEditWidget("", theme, cellWidth, messageWhenEmptyAndNotSelected: "Name".Localize()) + editSelectedName = new MHTextEditWidget("", theme, cellEditNameWidth, messageWhenEmptyAndNotSelected: "Name".Localize()) { HAnchor = HAnchor.Absolute, - SelectAllOnFocus = true, }; editSelectedName.ActualTextEditWidget.EditComplete += SelectedName_EditComplete; editSelectionGroup.AddChild(editSelectedName); - editSelectedExpression = new MHTextEditWidget("", theme, messageWhenEmptyAndNotSelected: "Select cell to edit".Localize()) + EditSelectedExpression = new MHTextEditWidget("", theme, messageWhenEmptyAndNotSelected: "Select cell to edit".Localize()) { HAnchor = HAnchor.Stretch, - SelectAllOnFocus = true, }; - editSelectionGroup.AddChild(editSelectedExpression); - editSelectedExpression.ActualTextEditWidget.EditComplete += ActualTextEditWidget_EditComplete1; + editSelectionGroup.AddChild(EditSelectedExpression); + EditSelectedExpression.ActualTextEditWidget.EditComplete += ActualTextEditWidget_EditComplete1; - var dataGrid = new GridWidget(sheetData.Width + 1, sheetData.Height + 1, theme: theme); + gridWidget = new GridWidget(sheetData.Width + 1, sheetData.Height + 1, theme: theme); - this.AddChild(dataGrid); + this.AddChild(gridWidget); for (int x = 0; x < sheetData.Width; x++) { - dataGrid.GetCell(x + 1, 0).AddChild(new TextWidget(((char)('A' + x)).ToString()) + var letterCell = gridWidget.GetCell(x + 1, 0); + letterCell.AddChild(new TextWidget(((char)('A' + x)).ToString()) { HAnchor = HAnchor.Center, VAnchor = VAnchor.Center, TextColor = theme.TextColor, }); + + letterCell.BackgroundColor = theme.SlightShade; } - dataGrid.SetColumnWidth(0, 20); + gridWidget.SetColumnWidth(0, 20); for (int y = 0; y < sheetData.Height; y++) { // add row count - dataGrid.GetCell(0, y + 1).AddChild(new TextWidget((y + 1).ToString()) + var numCell = gridWidget.GetCell(0, y + 1); + numCell.AddChild(new TextWidget((y + 1).ToString()) { TextColor = theme.TextColor, - VAnchor = VAnchor.Center + VAnchor = VAnchor.Center, }); + numCell.BackgroundColor = theme.SlightShade; + for (int x = 0; x < sheetData.Width; x++) { var capturedX = x; var capturedY = y; - var edit = new MHTextEditWidget(sheetData[x, y].Expression, theme) - { - HAnchor = HAnchor.Stretch, - SelectAllOnFocus = true, - }; - - edit.VAnchor |= VAnchor.Center; + var edit = new SheetFieldWidget(this, x, y, theme); CellWidgetsByLocation.Add((capturedX, capturedY), edit); - edit.MouseDown += (s, e) => SelectCell(capturedX, capturedY); + edit.MouseUp += (s, e) => SelectCell(capturedX, capturedY); - dataGrid.GetCell(x + 1, y + 1).AddChild(edit); - edit.ActualTextEditWidget.EditComplete += (s, e) => - { - editSelectedExpression.Text = edit.Text; - sheetData[capturedX, capturedY].Expression = edit.Text; - sheetData.Recalculate(); - }; + gridWidget.GetCell(x + 1, y + 1).AddChild(edit); } - dataGrid.ExpandToFitContent(); + gridWidget.ExpandToFitContent(); } } @@ -140,8 +135,9 @@ namespace MatterHackers.MatterControl.DesignTools return; } - sheetData[selectedCell.x, selectedCell.y].Expression = editSelectedExpression.Text; - CellWidgetsByLocation[(selectedCell.x, selectedCell.y)].Text = editSelectedExpression.Text; + SheetData[selectedCell.x, selectedCell.y].Expression = EditSelectedExpression.Text; + CellWidgetsByLocation[(selectedCell.x, selectedCell.y)].Text = EditSelectedExpression.Text; + SheetData.Recalculate(); } private void SelectedName_EditComplete(object sender, EventArgs e) @@ -152,13 +148,13 @@ namespace MatterHackers.MatterControl.DesignTools } var existingNames = new HashSet(); - for (int y = 0; y < sheetData.Height; y++) + for (int y = 0; y < SheetData.Height; y++) { - for (int x = 0; x < sheetData.Width; x++) + for (int x = 0; x < SheetData.Width; x++) { if (x != selectedCell.x || y != selectedCell.y) { - var currentName = sheetData[x, y].Name; + var currentName = SheetData[x, y].Name; if (!string.IsNullOrEmpty(currentName)) { existingNames.Add(currentName); @@ -169,12 +165,36 @@ namespace MatterHackers.MatterControl.DesignTools var name = agg_basics.GetNonCollidingName(editSelectedName.Text, existingNames); editSelectedName.Text = name; - sheetData[selectedCell.x, selectedCell.y].Name = name; - sheetData.Recalculate(); + SheetData[selectedCell.x, selectedCell.y].Name = name; + SheetData.Recalculate(); } - private void SelectCell(int x, int y) + public override void OnDraw(Graphics2D graphics2D) { + base.OnDraw(graphics2D); + + // draw the selected widget + var x = selectedCell.x; + var y = selectedCell.y; + if (x < 0 || x >= SheetData.Width || y < 0 || y >= SheetData.Height) + { + // out of bounds + return; + } + + var cell = gridWidget.GetCell(x + 1, y + 1); + var bounds = cell.TransformToParentSpace(this, cell.LocalBounds); + graphics2D.Rectangle(bounds, theme.PrimaryAccentColor); + } + + public void SelectCell(int x, int y) + { + if (x < 0 || x >= SheetData.Width || y < 0 || y >= SheetData.Height) + { + // out of bounds + return; + } + if (selectedCell.x != -1) { CellWidgetsByLocation[(selectedCell.x, selectedCell.y)].BorderColor = Color.Transparent; @@ -182,15 +202,17 @@ namespace MatterHackers.MatterControl.DesignTools selectedCell.x = x; selectedCell.y = y; CellWidgetsByLocation[(selectedCell.x, selectedCell.y)].BorderColor = theme.PrimaryAccentColor; - editSelectedExpression.Text = sheetData[x, y].Expression; - if (string.IsNullOrEmpty(sheetData[x, y].Name)) + EditSelectedExpression.Text = SheetData[x, y].Expression; + if (string.IsNullOrEmpty(SheetData[x, y].Name)) { editSelectedName.Text = $"{(char)('A' + x)}{y + 1}"; } else { - editSelectedName.Text = sheetData[x, y].Name; + editSelectedName.Text = SheetData[x, y].Name; } + + gridWidget.GetCell(x + 1, y + 1).Children.FirstOrDefault()?.Focus(); } } diff --git a/MatterControlLib/PartPreviewWindow/View3D/Actions/SheetFieldWidget.cs b/MatterControlLib/PartPreviewWindow/View3D/Actions/SheetFieldWidget.cs new file mode 100644 index 000000000..104071348 --- /dev/null +++ b/MatterControlLib/PartPreviewWindow/View3D/Actions/SheetFieldWidget.cs @@ -0,0 +1,284 @@ +/* +Copyright (c) 2018, Lars Brubaker, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using MatterHackers.Agg.UI; + +namespace MatterHackers.MatterControl.DesignTools +{ + public class SheetFieldWidget : GuiWidget + { + private SheetEditorWidget sheetEditorWidget; + private int x; + private int y; + private TextWidget content; + private string undoContent; + + private SheetData SheetData => sheetEditorWidget.SheetData; + + private enum EditModes + { + Unknown, + QuickText, + FullEdit + } + + private EditModes EditMode { get; set; } + + public SheetFieldWidget(SheetEditorWidget sheetEditorWidget, int x, int y, ThemeConfig theme) + { + this.sheetEditorWidget = sheetEditorWidget; + this.x = x; + this.y = y; + this.Name = $"Cell {x},{y}"; + + HAnchor = HAnchor.Stretch; + VAnchor = VAnchor.Stretch; + Selectable = true; + + content = new TextWidget("") + { + TextColor = theme.TextColor, + AutoExpandBoundsToText = true, + VAnchor = VAnchor.Bottom, + }; + + SheetData.Recalculated += (s, e) => + { + UpdateContents(); + }; + + UpdateContents(); + + this.AddChild(content); + } + + private void UpdateContents() + { + var expression = SheetData[x, y].Expression; + if (expression.StartsWith("=")) + { + content.Text = SheetData.EvaluateExpression(expression); + } + else + { + content.Text = expression; + } + } + + public override void OnKeyPress(KeyPressEventArgs keyPressEvent) + { + // this must be called first to ensure we get the correct Handled state + base.OnKeyPress(keyPressEvent); + if (!keyPressEvent.Handled) + { + if (keyPressEvent.KeyChar < 32 + && keyPressEvent.KeyChar != 13 + && keyPressEvent.KeyChar != 9) + { + return; + } + + if (EditMode == EditModes.Unknown) + { + EditMode = EditModes.QuickText; + this.content.Text = ""; + } + + this.content.Text += keyPressEvent.KeyChar.ToString(); + + UpdateSheetEditField(); + } + } + + private void UpdateSheetEditField() + { + this.sheetEditorWidget.EditSelectedExpression.Text = this.content.Text; + } + + public override void OnKeyDown(KeyEventArgs keyEvent) + { + // handle the tab early so it does not get consumed with switching to the next widget + if (!keyEvent.Handled) + { + switch (keyEvent.KeyCode) + { + case Keys.Tab: + Navigate(1, 0); + keyEvent.SuppressKeyPress = true; + keyEvent.Handled = true; + break; + } + } + + + // this must be called first to ensure we get the correct Handled state + base.OnKeyDown(keyEvent); + + if (!keyEvent.Handled) + { + switch (keyEvent.KeyCode) + { + case Keys.Escape: + // reset the selction to what it was before we started editing + this.content.Text = undoContent; + keyEvent.SuppressKeyPress = true; + keyEvent.Handled = true; + // and go back to the unknown state + EditMode = EditModes.Unknown; + UpdateSheetEditField(); + break; + + case Keys.Left: + Navigate(-1, 0); + keyEvent.SuppressKeyPress = true; + keyEvent.Handled = true; + break; + + case Keys.Down: + Navigate(0, 1); + keyEvent.SuppressKeyPress = true; + keyEvent.Handled = true; + break; + + case Keys.Right: + Navigate(1, 0); + keyEvent.SuppressKeyPress = true; + keyEvent.Handled = true; + break; + + case Keys.Up: + Navigate(0, -1); + keyEvent.SuppressKeyPress = true; + keyEvent.Handled = true; + break; + + case Keys.Enter: + switch (EditMode) + { + // go into full edit + case EditModes.Unknown: + EditMode = EditModes.FullEdit; + break; + + // finish edit and move down + case EditModes.QuickText: + case EditModes.FullEdit: + Navigate(0, 1); + // make sure we know we are edit complete + EditComplete(); + break; + } + + keyEvent.SuppressKeyPress = true; + keyEvent.Handled = true; + break; + + case Keys.Delete: + switch (EditMode) + { + case EditModes.Unknown: + // delete content + this.content.Text = ""; + break; + + case EditModes.QuickText: + // do nothing + break; + + case EditModes.FullEdit: + // delete from front + break; + } + keyEvent.SuppressKeyPress = true; + keyEvent.Handled = true; + UpdateSheetEditField(); + break; + + case Keys.Back: + switch (EditMode) + { + case EditModes.Unknown: + // delete text + this.content.Text = ""; + break; + + case EditModes.QuickText: + // delete from back + if (this.content.Text.Length > 0) + { + this.content.Text = this.content.Text.Substring(0, this.content.Text.Length - 1); + } + break; + + case EditModes.FullEdit: + // delete from back + break; + } + + keyEvent.SuppressKeyPress = true; + keyEvent.Handled = true; + UpdateSheetEditField(); + break; + } + } + } + + public override void OnFocusChanged(System.EventArgs e) + { + base.OnFocusChanged(e); + + if (this.Focused) + { + undoContent = this.content.Text; + EditMode = EditModes.Unknown; + } + else + { + EditComplete(); + } + } + + private void EditComplete() + { + if (this.content.Text != undoContent) + { + // make sure the is a sheet update + SheetData[x, y].Expression = this.content.Text; + SheetData.Recalculate(); + } + EditMode = EditModes.Unknown; + undoContent = this.content.Text; + } + + private void Navigate(int xOffset, int yOffset) + { + sheetEditorWidget.SelectCell(x + xOffset, y + yOffset); + } + } +}