From 24828e9922bc4587823987efc489290b780a5c3f Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Fri, 22 Mar 2024 09:56:19 -0400 Subject: [PATCH 01/21] Did tangent line conversion, reworked some of the UI, and started shifting. --- Base/Abstract/IEquationConvertible.cs | 8 ++ Base/Abstract/ITranslatable.cs | 3 + Base/Abstract/ITranslatableX.cs | 6 + Base/Abstract/ITranslatableXY.cs | 3 + Base/Abstract/ITranslatableY.cs | 6 + Base/Base.csproj.user | 3 + Base/Forms/GraphForm.Designer.cs | 98 ++++++++++---- Base/Forms/GraphForm.cs | 84 ++++++++++-- Base/Forms/TranslateForm.Designer.cs | 46 +++++++ Base/Forms/TranslateForm.cs | 25 ++++ Base/Forms/TranslateForm.resx | 120 ++++++++++++++++++ Base/Graphables/Equation.cs | 25 +++- Base/Graphables/TangentLine.cs | 36 +++++- .../PublishProfiles/FolderProfile.pubxml.user | 2 +- 14 files changed, 411 insertions(+), 54 deletions(-) create mode 100644 Base/Abstract/IEquationConvertible.cs create mode 100644 Base/Abstract/ITranslatable.cs create mode 100644 Base/Abstract/ITranslatableX.cs create mode 100644 Base/Abstract/ITranslatableXY.cs create mode 100644 Base/Abstract/ITranslatableY.cs create mode 100644 Base/Forms/TranslateForm.Designer.cs create mode 100644 Base/Forms/TranslateForm.cs create mode 100644 Base/Forms/TranslateForm.resx diff --git a/Base/Abstract/IEquationConvertible.cs b/Base/Abstract/IEquationConvertible.cs new file mode 100644 index 0000000..11bcfc5 --- /dev/null +++ b/Base/Abstract/IEquationConvertible.cs @@ -0,0 +1,8 @@ +using Graphing.Graphables; + +namespace Graphing.Abstract; + +public interface IEquationConvertible +{ + public Equation ToEquation(); +} diff --git a/Base/Abstract/ITranslatable.cs b/Base/Abstract/ITranslatable.cs new file mode 100644 index 0000000..0f9e8a5 --- /dev/null +++ b/Base/Abstract/ITranslatable.cs @@ -0,0 +1,3 @@ +namespace Graphing.Abstract; + +public interface ITranslatable { } diff --git a/Base/Abstract/ITranslatableX.cs b/Base/Abstract/ITranslatableX.cs new file mode 100644 index 0000000..ed13456 --- /dev/null +++ b/Base/Abstract/ITranslatableX.cs @@ -0,0 +1,6 @@ +namespace Graphing.Abstract; + +public interface ITranslatableX : ITranslatable +{ + public double OffsetX { get; set; } +} diff --git a/Base/Abstract/ITranslatableXY.cs b/Base/Abstract/ITranslatableXY.cs new file mode 100644 index 0000000..6414127 --- /dev/null +++ b/Base/Abstract/ITranslatableXY.cs @@ -0,0 +1,3 @@ +namespace Graphing.Abstract; + +public interface ITranslatableXY : ITranslatableX, ITranslatableY { } diff --git a/Base/Abstract/ITranslatableY.cs b/Base/Abstract/ITranslatableY.cs new file mode 100644 index 0000000..f7ad103 --- /dev/null +++ b/Base/Abstract/ITranslatableY.cs @@ -0,0 +1,6 @@ +namespace Graphing.Abstract; + +public interface ITranslatableY : ITranslatable +{ + public double OffsetY { get; set; } +} diff --git a/Base/Base.csproj.user b/Base/Base.csproj.user index ef577eb..b8c9d44 100644 --- a/Base/Base.csproj.user +++ b/Base/Base.csproj.user @@ -16,6 +16,9 @@ Form + + Form + Form diff --git a/Base/Forms/GraphForm.Designer.cs b/Base/Forms/GraphForm.Designer.cs index f30584b..6ad42fb 100644 --- a/Base/Forms/GraphForm.Designer.cs +++ b/Base/Forms/GraphForm.Designer.cs @@ -38,13 +38,18 @@ namespace Graphing.Forms ButtonViewportSetCenter = new ToolStripMenuItem(); ButtonViewportReset = new ToolStripMenuItem(); ButtonViewportResetWindow = new ToolStripMenuItem(); - MenuColors = new ToolStripMenuItem(); - MenuEquations = new ToolStripMenuItem(); - MenuEquationsDerivative = new ToolStripMenuItem(); - MenuEquationsIntegral = new ToolStripMenuItem(); + MenuElements = new ToolStripMenuItem(); + MenuElementsColors = new ToolStripMenuItem(); + MenuElementsRemove = new ToolStripMenuItem(); + MenuOperations = new ToolStripMenuItem(); + MenuOperationsDerivative = new ToolStripMenuItem(); + MenuOperationsIntegral = new ToolStripMenuItem(); + MenuConvert = new ToolStripMenuItem(); + MenuConvertEquation = new ToolStripMenuItem(); MenuMisc = new ToolStripMenuItem(); MenuMiscCaches = new ToolStripMenuItem(); MiscMenuPreload = new ToolStripMenuItem(); + MenuOperationsTranslate = new ToolStripMenuItem(); GraphMenu.SuspendLayout(); SuspendLayout(); // @@ -64,7 +69,7 @@ namespace Graphing.Forms // GraphMenu // GraphMenu.ImageScalingSize = new Size(32, 32); - GraphMenu.Items.AddRange(new ToolStripItem[] { MenuViewport, MenuColors, MenuEquations, MenuMisc }); + GraphMenu.Items.AddRange(new ToolStripItem[] { MenuViewport, MenuElements, MenuOperations, MenuConvert, MenuMisc }); GraphMenu.Location = new Point(0, 0); GraphMenu.Name = "GraphMenu"; GraphMenu.Size = new Size(1449, 42); @@ -106,30 +111,56 @@ namespace Graphing.Forms ButtonViewportResetWindow.Text = "Reset Window Size"; ButtonViewportResetWindow.Click += ButtonViewportResetWindow_Click; // - // MenuColors + // MenuElements // - MenuColors.Name = "MenuColors"; - MenuColors.Size = new Size(101, 38); - MenuColors.Text = "Colors"; + MenuElements.DropDownItems.AddRange(new ToolStripItem[] { MenuElementsColors, MenuElementsRemove }); + MenuElements.Name = "MenuElements"; + MenuElements.Size = new Size(131, 38); + MenuElements.Text = "Elements"; // - // MenuEquations + // MenuElementsColors // - MenuEquations.DropDownItems.AddRange(new ToolStripItem[] { MenuEquationsDerivative, MenuEquationsIntegral }); - MenuEquations.Name = "MenuEquations"; - MenuEquations.Size = new Size(138, 38); - MenuEquations.Text = "Equations"; + MenuElementsColors.Name = "MenuElementsColors"; + MenuElementsColors.Size = new Size(359, 44); + MenuElementsColors.Text = "Colors"; // - // MenuEquationsDerivative + // MenuElementsRemove // - MenuEquationsDerivative.Name = "MenuEquationsDerivative"; - MenuEquationsDerivative.Size = new Size(360, 44); - MenuEquationsDerivative.Text = "Compute Derivative"; + MenuElementsRemove.Name = "MenuElementsRemove"; + MenuElementsRemove.Size = new Size(359, 44); + MenuElementsRemove.Text = "Remove"; // - // MenuEquationsIntegral + // MenuOperations // - MenuEquationsIntegral.Name = "MenuEquationsIntegral"; - MenuEquationsIntegral.Size = new Size(360, 44); - MenuEquationsIntegral.Text = "Compute Integral"; + MenuOperations.DropDownItems.AddRange(new ToolStripItem[] { MenuOperationsDerivative, MenuOperationsIntegral, MenuOperationsTranslate }); + MenuOperations.Name = "MenuOperations"; + MenuOperations.Size = new Size(151, 38); + MenuOperations.Text = "Operations"; + // + // MenuOperationsDerivative + // + MenuOperationsDerivative.Name = "MenuOperationsDerivative"; + MenuOperationsDerivative.Size = new Size(360, 44); + MenuOperationsDerivative.Text = "Compute Derivative"; + // + // MenuOperationsIntegral + // + MenuOperationsIntegral.Name = "MenuOperationsIntegral"; + MenuOperationsIntegral.Size = new Size(360, 44); + MenuOperationsIntegral.Text = "Compute Integral"; + // + // MenuConvert + // + MenuConvert.DropDownItems.AddRange(new ToolStripItem[] { MenuConvertEquation }); + MenuConvert.Name = "MenuConvert"; + MenuConvert.Size = new Size(118, 38); + MenuConvert.Text = "Convert"; + // + // MenuConvertEquation + // + MenuConvertEquation.Name = "MenuConvertEquation"; + MenuConvertEquation.Size = new Size(273, 44); + MenuConvertEquation.Text = "To Equation"; // // MenuMisc // @@ -141,17 +172,23 @@ namespace Graphing.Forms // MenuMiscCaches // MenuMiscCaches.Name = "MenuMiscCaches"; - MenuMiscCaches.Size = new Size(359, 44); + MenuMiscCaches.Size = new Size(299, 44); MenuMiscCaches.Text = "View Caches"; MenuMiscCaches.Click += MenuMiscCaches_Click; // // MiscMenuPreload // MiscMenuPreload.Name = "MiscMenuPreload"; - MiscMenuPreload.Size = new Size(359, 44); + MiscMenuPreload.Size = new Size(299, 44); MiscMenuPreload.Text = "Preload Cache"; MiscMenuPreload.Click += MiscMenuPreload_Click; // + // MenuOperationsTranslate + // + MenuOperationsTranslate.Name = "MenuOperationsTranslate"; + MenuOperationsTranslate.Size = new Size(360, 44); + MenuOperationsTranslate.Text = "Translate"; + // // GraphForm // AutoScaleDimensions = new SizeF(13F, 32F); @@ -172,17 +209,22 @@ namespace Graphing.Forms private Button ResetViewportButton; private MenuStrip GraphMenu; - private ToolStripMenuItem MenuColors; private ToolStripMenuItem MenuViewport; private ToolStripMenuItem ButtonViewportSetZoom; private ToolStripMenuItem ButtonViewportSetCenter; private ToolStripMenuItem ButtonViewportReset; private ToolStripMenuItem ButtonViewportResetWindow; - private ToolStripMenuItem MenuEquations; - private ToolStripMenuItem MenuEquationsDerivative; - private ToolStripMenuItem MenuEquationsIntegral; + private ToolStripMenuItem MenuOperations; + private ToolStripMenuItem MenuOperationsDerivative; + private ToolStripMenuItem MenuOperationsIntegral; private ToolStripMenuItem MenuMisc; private ToolStripMenuItem MenuMiscCaches; private ToolStripMenuItem MiscMenuPreload; + private ToolStripMenuItem MenuConvert; + private ToolStripMenuItem MenuConvertEquation; + private ToolStripMenuItem MenuElements; + private ToolStripMenuItem MenuElementsColors; + private ToolStripMenuItem MenuElementsRemove; + private ToolStripMenuItem MenuOperationsTranslate; } } \ No newline at end of file diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 152d852..b1b0588 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -255,9 +255,15 @@ public partial class GraphForm : Form Invalidate(false); } - public void Graph(params Graphable[] able) + public void Graph(params Graphable[] newAbles) { - ables.AddRange(able); + ables.AddRange(newAbles); + RegenerateMenuItems(); + Invalidate(false); + } + public void Ungraph(params Graphable[] ables) + { + this.ables.RemoveAll(x => ables.Contains(x)); RegenerateMenuItems(); Invalidate(false); } @@ -348,9 +354,12 @@ public partial class GraphForm : Form private void RegenerateMenuItems() { - MenuColors.DropDownItems.Clear(); - MenuEquationsDerivative.DropDownItems.Clear(); - MenuEquationsIntegral.DropDownItems.Clear(); + MenuElementsColors.DropDownItems.Clear(); + MenuElementsRemove.DropDownItems.Clear(); + MenuOperationsDerivative.DropDownItems.Clear(); + MenuOperationsIntegral.DropDownItems.Clear(); + MenuConvertEquation.DropDownItems.Clear(); + MenuOperationsTranslate.DropDownItems.Clear(); foreach (Graphable able in ables) { @@ -360,7 +369,15 @@ public partial class GraphForm : Form Text = able.Name }; colorItem.Click += (o, e) => GraphColorPickerButton_Click(able); - MenuColors.DropDownItems.Add(colorItem); + MenuElementsColors.DropDownItems.Add(colorItem); + + ToolStripMenuItem removeItem = new() + { + ForeColor = able.Color, + Text = able.Name + }; + removeItem.Click += (o, e) => Ungraph(able); + MenuElementsRemove.DropDownItems.Add(removeItem); if (able is IDerivable derivable) { @@ -370,7 +387,7 @@ public partial class GraphForm : Form Text = able.Name }; derivativeItem.Click += (o, e) => Graph(derivable.Derive()); - MenuEquationsDerivative.DropDownItems.Add(derivativeItem); + MenuOperationsDerivative.DropDownItems.Add(derivativeItem); } if (able is IIntegrable integrable) { @@ -380,20 +397,48 @@ public partial class GraphForm : Form Text = able.Name }; integralItem.Click += (o, e) => Graph(integrable.Integrate()); - MenuEquationsIntegral.DropDownItems.Add(integralItem); + MenuOperationsIntegral.DropDownItems.Add(integralItem); + } + if (able is IEquationConvertible equConvert) + { + ToolStripMenuItem equItem = new() + { + ForeColor = able.Color, + Text = able.Name + }; + equItem.Click += (o, e) => + { + Ungraph(able); + Graph(equConvert.ToEquation()); + }; + MenuConvertEquation.DropDownItems.Add(equItem); + } + if (able is ITranslatable translatable) + { + ToolStripMenuItem transItem = new() + { + ForeColor = able.Color, + Text = able.Name + }; + transItem.Click += (o, e) => ElementsOperationsTranslate_Click(able, translatable); + MenuOperationsTranslate.DropDownItems.Add(transItem); } } } private void ButtonViewportSetZoom_Click(object? sender, EventArgs e) { - SetZoomForm picker = new(this) + SetZoomForm zoomer = new(this) { StartPosition = FormStartPosition.Manual, }; - picker.Location = new Point(Location.X + ClientRectangle.Width + 10, - Location.Y + (ClientRectangle.Height - picker.ClientRectangle.Height) / 2); - picker.ShowDialog(); + zoomer.Location = new Point(Location.X + ClientRectangle.Width + 10, + Location.Y + (ClientRectangle.Height - zoomer.ClientRectangle.Height) / 2); + if (zoomer.Location.X + zoomer.Width > Screen.FromControl(this).WorkingArea.Width) + { + zoomer.StartPosition = FormStartPosition.WindowsDefaultLocation; + } + zoomer.ShowDialog(); } private void ButtonViewportSetCenter_Click(object? sender, EventArgs e) { @@ -446,4 +491,19 @@ public partial class GraphForm : Form foreach (Graphable able in Graphables) able.Preload(xRange, yRange, step); Invalidate(false); } + + private void ElementsOperationsTranslate_Click(Graphable ableRaw, ITranslatable ableTrans) + { + TranslateForm shifter = new(this, ableRaw, ableTrans) + { + StartPosition = FormStartPosition.Manual, + }; + shifter.Location = new Point(Location.X + ClientRectangle.Width + 10, + Location.Y + (ClientRectangle.Height - shifter.ClientRectangle.Height) / 2); + if (shifter.Location.X + shifter.Width > Screen.FromControl(this).WorkingArea.Width) + { + shifter.StartPosition = FormStartPosition.WindowsDefaultLocation; + } + shifter.ShowDialog(); + } } diff --git a/Base/Forms/TranslateForm.Designer.cs b/Base/Forms/TranslateForm.Designer.cs new file mode 100644 index 0000000..058a9fe --- /dev/null +++ b/Base/Forms/TranslateForm.Designer.cs @@ -0,0 +1,46 @@ +namespace Graphing.Forms +{ + partial class TranslateForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + SuspendLayout(); + // + // TranslateForm + // + AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(674, 629); + FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + Name = "TranslateForm"; + Text = "TranslateForm"; + ResumeLayout(false); + } + + #endregion + } +} \ No newline at end of file diff --git a/Base/Forms/TranslateForm.cs b/Base/Forms/TranslateForm.cs new file mode 100644 index 0000000..775b8ce --- /dev/null +++ b/Base/Forms/TranslateForm.cs @@ -0,0 +1,25 @@ +using Graphing.Abstract; +using System.Windows.Forms; + +namespace Graphing.Forms; + +public partial class TranslateForm : Form +{ + private readonly GraphForm refForm; + + // These variables both represent the same graphable. + private readonly Graphable ableRaw; + private readonly ITranslatable ableTrans; + + public TranslateForm(GraphForm graph, Graphable ableRaw, ITranslatable ableTrans) + { + refForm = graph; + this.ableRaw = ableRaw; + this.ableTrans = ableTrans; + + if (ableTrans is ITranslatableX transX) transX.OffsetX = 1; + if (ableTrans is ITranslatableY transY) transY.OffsetY = 1; + + graph.Invalidate(false); + } +} diff --git a/Base/Forms/TranslateForm.resx b/Base/Forms/TranslateForm.resx new file mode 100644 index 0000000..af32865 --- /dev/null +++ b/Base/Forms/TranslateForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index c3dfa4e..da7fa61 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -6,13 +6,18 @@ using System.Collections.Generic; namespace Graphing.Graphables; -public class Equation : Graphable, IIntegrable, IDerivable +public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY { private static int equationNum; + public double OffsetX { get; set; } + public double OffsetY { get; set; } + protected readonly EquationDelegate equ; protected readonly List cache; + public event Action OnInvalidate; + public Equation(EquationDelegate equ) { equationNum++; @@ -20,6 +25,11 @@ public class Equation : Graphable, IIntegrable, IDerivable this.equ = equ; cache = []; + + OffsetX = 0; + OffsetY = 0; + + OnInvalidate = delegate { }; } public override IEnumerable GetItemsToRender(in GraphForm graph) @@ -46,6 +56,7 @@ public class Equation : Graphable, IIntegrable, IDerivable previousX = currentX; previousY = currentY; } + OnInvalidate.Invoke(graph); return lines; } @@ -61,16 +72,18 @@ public class Equation : Graphable, IIntegrable, IDerivable public override void EraseCache() => cache.Clear(); protected double GetFromCache(double x, double epsilon) { - (double dist, double nearest, int index) = NearestCachedPoint(x); - if (dist < epsilon) return nearest; + (double dist, double nearest, int index) = NearestCachedPoint(x - OffsetX); + if (dist < epsilon) return nearest + OffsetY; else { - double result = equ(x); - cache.Insert(index + 1, new(x, result)); - return result; + double result = equ(x - OffsetX); + cache.Insert(index + 1, new(x - OffsetX, result)); + return result + OffsetY; } } + public double GetValueAt(double x) => GetFromCache(x, 0); + protected (double dist, double y, int index) NearestCachedPoint(double x) { if (cache.Count == 0) return (double.PositiveInfinity, double.NaN, -1); diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs index f22eec0..9f5c2e1 100644 --- a/Base/Graphables/TangentLine.cs +++ b/Base/Graphables/TangentLine.cs @@ -1,11 +1,12 @@ -using Graphing.Forms; +using Graphing.Abstract; +using Graphing.Forms; using Graphing.Parts; using System; using System.Collections.Generic; namespace Graphing.Graphables; -public class TangentLine : Graphable +public class TangentLine : Graphable, IEquationConvertible, ITranslatableX { public double Position { @@ -18,8 +19,13 @@ public class TangentLine : Graphable } private double _position; // Private because it has exactly the same functionality as `Position`. + public double OffsetX + { + get => Position; + set => Position = value; + } + protected readonly Equation parent; - protected readonly EquationDelegate parentEqu; protected readonly double length; @@ -35,10 +41,16 @@ public class TangentLine : Graphable Name = $"Tangent Line of {parent.Name}"; slopeCache = []; - parentEqu = parent.GetDelegate(); - Position = position; this.length = length; this.parent = parent; + Position = position; + + parent.OnInvalidate += (graph) => + { + // I don't love this but it works. + EraseCache(); + Position = _position; // Done for side effects. + }; } public override IEnumerable GetItemsToRender(in GraphForm graph) @@ -63,8 +75,8 @@ public class TangentLine : Graphable const double step = 1e-3; - double initial = parentEqu(x); - Float2 result = new((parentEqu(x + step) - initial) / step, initial); + double initial = parent.GetValueAt(x); + Float2 result = new((parent.GetValueAt(x + step) - initial) / step, initial); slopeCache.Add(x, result); return result; } @@ -112,4 +124,14 @@ public class TangentLine : Graphable // that can be changed. for (double x = xRange.x; x <= xRange.y; x += step) DerivativeAtPoint(x); } + + public Equation ToEquation() + { + double slope = currentSlope.x, x1 = Position, y1 = currentSlope.y; + return new(x => slope * (x - x1) + y1) + { + Name = Name, + Color = Color + }; + } } diff --git a/Base/Properties/PublishProfiles/FolderProfile.pubxml.user b/Base/Properties/PublishProfiles/FolderProfile.pubxml.user index 706348e..4da2d5e 100644 --- a/Base/Properties/PublishProfiles/FolderProfile.pubxml.user +++ b/Base/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - True|2024-03-20T12:39:01.6402921Z;True|2024-03-13T10:31:43.4569441-04:00;False|2024-03-13T10:30:01.4347009-04:00;False|2024-03-13T10:27:31.9554551-04:00; + True|2024-03-20T12:48:45.8740885Z;True|2024-03-20T08:48:35.6948867-04:00;True|2024-03-20T08:39:01.6402921-04:00;True|2024-03-13T10:31:43.4569441-04:00;False|2024-03-13T10:30:01.4347009-04:00;False|2024-03-13T10:27:31.9554551-04:00; \ No newline at end of file -- 2.49.0.windows.1 From 30529673d029ab3fabff621614201fbcbf524dd8 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Sat, 23 Mar 2024 08:04:41 -0400 Subject: [PATCH 02/21] Offsetter is almost done. --- Base/Forms/TranslateForm.Designer.cs | 161 +++++++++++++++- Base/Forms/TranslateForm.cs | 269 ++++++++++++++++++++++++++- 2 files changed, 425 insertions(+), 5 deletions(-) diff --git a/Base/Forms/TranslateForm.Designer.cs b/Base/Forms/TranslateForm.Designer.cs index 058a9fe..b6d756a 100644 --- a/Base/Forms/TranslateForm.Designer.cs +++ b/Base/Forms/TranslateForm.Designer.cs @@ -28,19 +28,176 @@ /// private void InitializeComponent() { + TrackX = new System.Windows.Forms.TrackBar(); + LabelX = new System.Windows.Forms.Label(); + MinBoxX = new System.Windows.Forms.TextBox(); + MaxBoxX = new System.Windows.Forms.TextBox(); + ThisValueX = new System.Windows.Forms.TextBox(); + ThisValueY = new System.Windows.Forms.TextBox(); + MaxBoxY = new System.Windows.Forms.TextBox(); + MinBoxY = new System.Windows.Forms.TextBox(); + LabelY = new System.Windows.Forms.Label(); + TrackY = new System.Windows.Forms.TrackBar(); + TitleLabel = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)TrackX).BeginInit(); + ((System.ComponentModel.ISupportInitialize)TrackY).BeginInit(); SuspendLayout(); // + // TrackX + // + TrackX.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + TrackX.LargeChange = 250; + TrackX.Location = new System.Drawing.Point(15, 193); + TrackX.Margin = new System.Windows.Forms.Padding(0); + TrackX.Maximum = 1000; + TrackX.Name = "TrackX"; + TrackX.Size = new System.Drawing.Size(644, 90); + TrackX.SmallChange = 50; + TrackX.TabIndex = 0; + TrackX.TabStop = false; + TrackX.TickFrequency = 50; + TrackX.TickStyle = System.Windows.Forms.TickStyle.Both; + TrackX.Value = 1; + TrackX.Scroll += TrackX_Scroll; + // + // LabelX + // + LabelX.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + LabelX.Location = new System.Drawing.Point(15, 157); + LabelX.Name = "LabelX"; + LabelX.Size = new System.Drawing.Size(644, 36); + LabelX.TabIndex = 1; + LabelX.Text = "X Offset"; + LabelX.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // MinBoxX + // + MinBoxX.Location = new System.Drawing.Point(15, 259); + MinBoxX.Name = "MinBoxX"; + MinBoxX.Size = new System.Drawing.Size(100, 39); + MinBoxX.TabIndex = 2; + // + // MaxBoxX + // + MaxBoxX.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; + MaxBoxX.Location = new System.Drawing.Point(556, 259); + MaxBoxX.Name = "MaxBoxX"; + MaxBoxX.Size = new System.Drawing.Size(100, 39); + MaxBoxX.TabIndex = 3; + MaxBoxX.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // ThisValueX + // + ThisValueX.Anchor = System.Windows.Forms.AnchorStyles.Top; + ThisValueX.Location = new System.Drawing.Point(289, 259); + ThisValueX.Name = "ThisValueX"; + ThisValueX.Size = new System.Drawing.Size(100, 39); + ThisValueX.TabIndex = 4; + ThisValueX.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + // + // ThisValueY + // + ThisValueY.Anchor = System.Windows.Forms.AnchorStyles.Top; + ThisValueY.Location = new System.Drawing.Point(289, 449); + ThisValueY.Name = "ThisValueY"; + ThisValueY.Size = new System.Drawing.Size(100, 39); + ThisValueY.TabIndex = 9; + ThisValueY.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + // + // MaxBoxY + // + MaxBoxY.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; + MaxBoxY.Location = new System.Drawing.Point(556, 449); + MaxBoxY.Name = "MaxBoxY"; + MaxBoxY.Size = new System.Drawing.Size(100, 39); + MaxBoxY.TabIndex = 8; + MaxBoxY.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // MinBoxY + // + MinBoxY.Location = new System.Drawing.Point(15, 449); + MinBoxY.Name = "MinBoxY"; + MinBoxY.Size = new System.Drawing.Size(100, 39); + MinBoxY.TabIndex = 7; + // + // LabelY + // + LabelY.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + LabelY.Location = new System.Drawing.Point(15, 347); + LabelY.Name = "LabelY"; + LabelY.Size = new System.Drawing.Size(644, 36); + LabelY.TabIndex = 6; + LabelY.Text = "Y Offset"; + LabelY.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // TrackY + // + TrackY.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + TrackY.LargeChange = 250; + TrackY.Location = new System.Drawing.Point(15, 383); + TrackY.Margin = new System.Windows.Forms.Padding(0); + TrackY.Maximum = 1000; + TrackY.Name = "TrackY"; + TrackY.Size = new System.Drawing.Size(644, 90); + TrackY.SmallChange = 50; + TrackY.TabIndex = 5; + TrackY.TabStop = false; + TrackY.TickFrequency = 50; + TrackY.TickStyle = System.Windows.Forms.TickStyle.Both; + TrackY.Value = 1; + TrackY.Scroll += TrackY_Scroll; + // + // TitleLabel + // + TitleLabel.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + TitleLabel.Location = new System.Drawing.Point(12, 39); + TitleLabel.Name = "TitleLabel"; + TitleLabel.Padding = new System.Windows.Forms.Padding(0, 0, 0, 18); + TitleLabel.Size = new System.Drawing.Size(644, 89); + TitleLabel.TabIndex = 10; + TitleLabel.Text = "Change the Location of %name%"; + TitleLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // // TranslateForm // AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - ClientSize = new System.Drawing.Size(674, 629); + AutoSize = true; + AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + ClientSize = new System.Drawing.Size(674, 531); + Controls.Add(TitleLabel); + Controls.Add(ThisValueY); + Controls.Add(MaxBoxY); + Controls.Add(MinBoxY); + Controls.Add(LabelY); + Controls.Add(TrackY); + Controls.Add(ThisValueX); + Controls.Add(MaxBoxX); + Controls.Add(MinBoxX); + Controls.Add(LabelX); + Controls.Add(TrackX); FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; Name = "TranslateForm"; - Text = "TranslateForm"; + Padding = new System.Windows.Forms.Padding(15); + Text = "Herm"; + ((System.ComponentModel.ISupportInitialize)TrackX).EndInit(); + ((System.ComponentModel.ISupportInitialize)TrackY).EndInit(); ResumeLayout(false); + PerformLayout(); } #endregion + + private System.Windows.Forms.TrackBar TrackX; + private System.Windows.Forms.Label LabelX; + private System.Windows.Forms.TextBox MinBoxX; + private System.Windows.Forms.TextBox MaxBoxX; + private System.Windows.Forms.TextBox ThisValueX; + private System.Windows.Forms.TextBox ThisValueY; + private System.Windows.Forms.TextBox MaxBoxY; + private System.Windows.Forms.TextBox MinBoxY; + private System.Windows.Forms.Label LabelY; + private System.Windows.Forms.TrackBar TrackY; + private System.Windows.Forms.Label TitleLabel; } } \ No newline at end of file diff --git a/Base/Forms/TranslateForm.cs b/Base/Forms/TranslateForm.cs index 775b8ce..cbb2567 100644 --- a/Base/Forms/TranslateForm.cs +++ b/Base/Forms/TranslateForm.cs @@ -1,4 +1,5 @@ using Graphing.Abstract; +using System; using System.Windows.Forms; namespace Graphing.Forms; @@ -10,16 +11,278 @@ public partial class TranslateForm : Form // These variables both represent the same graphable. private readonly Graphable ableRaw; private readonly ITranslatable ableTrans; + private readonly ITranslatableX? ableTransX; + private readonly ITranslatableY? ableTransY; + + private readonly bool useX; + private readonly bool useY; + + private double minX, maxX, curX, minY, maxY, curY; public TranslateForm(GraphForm graph, Graphable ableRaw, ITranslatable ableTrans) { + InitializeComponent(); + + Text = $"Translate {ableRaw.Name}"; + TitleLabel.Text = $"Adjust Location for {ableRaw.Name}"; + + MinBoxX.Leave += (o, e) => UpdateFromMinBoxY(); + MinBoxX.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) UpdateFromMinBoxY(); + }; + MaxBoxX.Leave += (o, e) => UpdateFromMaxBoxY(); + MaxBoxX.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) UpdateFromMaxBoxY(); + }; + ThisValueX.Leave += (o, e) => UpdateFromThisBoxY(); + ThisValueX.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) UpdateFromThisBoxY(); + }; + + MinBoxY.Leave += (o, e) => UpdateFromMinBoxY(); + MinBoxY.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) UpdateFromMinBoxY(); + }; + MaxBoxY.Leave += (o, e) => UpdateFromMaxBoxY(); + MaxBoxY.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) UpdateFromMaxBoxY(); + }; + ThisValueY.Leave += (o, e) => UpdateFromThisBoxY(); + ThisValueY.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) UpdateFromThisBoxY(); + }; + refForm = graph; this.ableRaw = ableRaw; this.ableTrans = ableTrans; - if (ableTrans is ITranslatableX transX) transX.OffsetX = 1; - if (ableTrans is ITranslatableY transY) transY.OffsetY = 1; + double curX = 0, curY = 0; + if (ableTrans is ITranslatableX transX) + { + useX = true; + ableTransX = transX; + curX = transX.OffsetX; + } + else + { + LabelY.Location = LabelX.Location; + TrackY.Location = TrackX.Location; + MinBoxY.Location = MinBoxX.Location; + MaxBoxY.Location = MaxBoxX.Location; + ThisValueY.Location = ThisValueX.Location; - graph.Invalidate(false); + LabelX.Dispose(); + TrackX.Dispose(); + MinBoxX.Dispose(); + MaxBoxX.Dispose(); + ThisValueX.Dispose(); + } + + if (ableTrans is ITranslatableY transY) + { + useY = true; + ableTransY = transY; + curY = transY.OffsetY; + } + else + { + LabelY.Dispose(); + TrackY.Dispose(); + MinBoxY.Dispose(); + MaxBoxY.Dispose(); + ThisValueY.Dispose(); + } + + if (!useX && !useY) + { + TitleLabel.Text = $"There doesn't seem to be anything you can translate for {ableRaw.Name}."; + } + + minX = -10; + maxX = 10; + minY = -10; + maxY = 10; + + UpdateFromCurX(curX, false); + UpdateFromCurY(curY, false); + } + + private void UpdateFromCurX(double newCurX, bool invalidate) + { + curX = newCurX; + if (curX < minX) minX = curX; + else if (curX > maxX) maxX = curX; + + int step = (int)(1000 * InverseLerp(minX, maxX, curX)); + TrackX.Value = step; + MinBoxX.Text = $"{minX:0.00}"; + MaxBoxX.Text = $"{maxX:0.00}"; + ThisValueX.Text = $"{curX:0.00}"; + + if (invalidate) refForm.Invalidate(false); + } + private void UpdateFromSliderX(bool invalidate) + { + double t = InverseLerp(0, 1000, TrackX.Value); + curX = Lerp(minX, maxX, t); + + ThisValueX.Text = $"{curX:0.00}"; + ableTransX!.OffsetX = curX; + + if (invalidate) refForm.Invalidate(false); + } + private void UpdateFromMinBoxX() + { + if (!double.TryParse(MinBoxX.Text, out double newMin)) + { + MinBoxX.Text = $"{minX:0.00}"; + return; + } + minX = newMin; + MinBoxX.Text = $"{minX:0.00}"; + + if (minX > curX) + { + curX = minX; + ThisValueX.Text = $"{curX:0.00}"; + ableTransX!.OffsetX = curX; + } + + int step = (int)(1000 * InverseLerp(minX, maxX, curX)); + TrackX.Value = step; + + refForm.Invalidate(false); + } + private void UpdateFromMaxBoxX() + { + if (!double.TryParse(MaxBoxX.Text, out double newMax)) + { + MaxBoxX.Text = $"{maxX:0.00}"; + return; + } + + maxX = newMax; + MaxBoxX.Text = $"{maxX:0.00}"; + + if (maxX < curX) + { + curX = maxX; + ThisValueX.Text = $"{curX:0.00}"; + ableTransX!.OffsetX = curX; + } + + int step = (int)(1000 * InverseLerp(minX, maxX, curX)); + TrackX.Value = step; + + refForm.Invalidate(false); + } + private void UpdateFromThisBoxX() + { + if (!double.TryParse(ThisValueX.Text, out double newCur)) + { + ThisValueX.Text = $"{curX:0.00}"; + return; + } + ableTransX!.OffsetX = newCur; + UpdateFromCurX(newCur, true); + } + + private void UpdateFromCurY(double newCurY, bool invalidate) + { + curY = newCurY; + if (curY < minY) minY = curY; + else if (curY > maxY) maxY = curY; + + int step = (int)(1000 * InverseLerp(minY, maxY, curY)); + TrackY.Value = step; + MinBoxY.Text = $"{minY:0.00}"; + MaxBoxY.Text = $"{maxY:0.00}"; + ThisValueY.Text = $"{curY:0.00}"; + + if (invalidate) refForm.Invalidate(false); + } + private void UpdateFromSliderY(bool invalidate) + { + double t = InverseLerp(0, 1000, TrackY.Value); + curY = Lerp(minY, maxY, t); + + ThisValueY.Text = $"{curY:0.00}"; + ableTransY!.OffsetY = curY; + + if (invalidate) refForm.Invalidate(false); + } + private void UpdateFromMinBoxY() + { + if (!double.TryParse(MinBoxY.Text, out double newMin)) + { + MinBoxY.Text = $"{minY:0.00}"; + return; + } + minY = newMin; + MinBoxY.Text = $"{minY:0.00}"; + + if (minY > curY) + { + curY = minY; + ThisValueY.Text = $"{curY:0.00}"; + ableTransY!.OffsetY = curY; + } + + int step = (int)(1000 * InverseLerp(minY, maxY, curY)); + TrackY.Value = step; + + refForm.Invalidate(false); + } + private void UpdateFromMaxBoxY() + { + if (!double.TryParse(MaxBoxY.Text, out double newMax)) + { + MaxBoxY.Text = $"{maxY:0.00}"; + return; + } + + maxY = newMax; + MaxBoxY.Text = $"{maxY:0.00}"; + + if (maxY < curY) + { + curY = maxY; + ThisValueY.Text = $"{curY:0.00}"; + ableTransY!.OffsetY = curY; + } + + int step = (int)(1000 * InverseLerp(minY, maxY, curY)); + TrackY.Value = step; + + refForm.Invalidate(false); + } + private void UpdateFromThisBoxY() + { + if (!double.TryParse(ThisValueY.Text, out double newCur)) + { + ThisValueY.Text = $"{curY:0.00}"; + return; + } + ableTransY!.OffsetY = newCur; + UpdateFromCurY(newCur, true); + } + + private static double Lerp(double a, double b, double t) => a + t * (b - a); + private static double InverseLerp(double a, double b, double c) => (c - a) / (b - a); + + private void TrackX_Scroll(object? sender, EventArgs e) + { + UpdateFromSliderX(true); + } + + private void TrackY_Scroll(object sender, EventArgs e) + { + UpdateFromSliderY(true); } } -- 2.49.0.windows.1 From 66146355c6321bbe42afb120d0db76bd18ec0f77 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Sat, 23 Mar 2024 09:47:54 -0400 Subject: [PATCH 03/21] Whoopsies. --- Base/Forms/GraphForm.cs | 2 +- Base/Forms/TranslateForm.Designer.cs | 1 + Base/Forms/TranslateForm.cs | 16 ++++++---------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index b1b0588..b266525 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -504,6 +504,6 @@ public partial class GraphForm : Form { shifter.StartPosition = FormStartPosition.WindowsDefaultLocation; } - shifter.ShowDialog(); + shifter.Show(); } } diff --git a/Base/Forms/TranslateForm.Designer.cs b/Base/Forms/TranslateForm.Designer.cs index b6d756a..2f9f7c6 100644 --- a/Base/Forms/TranslateForm.Designer.cs +++ b/Base/Forms/TranslateForm.Designer.cs @@ -180,6 +180,7 @@ Name = "TranslateForm"; Padding = new System.Windows.Forms.Padding(15); Text = "Herm"; + TopMost = true; ((System.ComponentModel.ISupportInitialize)TrackX).EndInit(); ((System.ComponentModel.ISupportInitialize)TrackY).EndInit(); ResumeLayout(false); diff --git a/Base/Forms/TranslateForm.cs b/Base/Forms/TranslateForm.cs index cbb2567..fdd2240 100644 --- a/Base/Forms/TranslateForm.cs +++ b/Base/Forms/TranslateForm.cs @@ -9,8 +9,6 @@ public partial class TranslateForm : Form private readonly GraphForm refForm; // These variables both represent the same graphable. - private readonly Graphable ableRaw; - private readonly ITranslatable ableTrans; private readonly ITranslatableX? ableTransX; private readonly ITranslatableY? ableTransY; @@ -26,20 +24,20 @@ public partial class TranslateForm : Form Text = $"Translate {ableRaw.Name}"; TitleLabel.Text = $"Adjust Location for {ableRaw.Name}"; - MinBoxX.Leave += (o, e) => UpdateFromMinBoxY(); + MinBoxX.Leave += (o, e) => UpdateFromMinBoxX(); MinBoxX.KeyDown += (o, e) => { - if (e.KeyCode == Keys.Enter) UpdateFromMinBoxY(); + if (e.KeyCode == Keys.Enter) UpdateFromMinBoxX(); }; - MaxBoxX.Leave += (o, e) => UpdateFromMaxBoxY(); + MaxBoxX.Leave += (o, e) => UpdateFromMaxBoxX(); MaxBoxX.KeyDown += (o, e) => { - if (e.KeyCode == Keys.Enter) UpdateFromMaxBoxY(); + if (e.KeyCode == Keys.Enter) UpdateFromMaxBoxX(); }; - ThisValueX.Leave += (o, e) => UpdateFromThisBoxY(); + ThisValueX.Leave += (o, e) => UpdateFromThisBoxX(); ThisValueX.KeyDown += (o, e) => { - if (e.KeyCode == Keys.Enter) UpdateFromThisBoxY(); + if (e.KeyCode == Keys.Enter) UpdateFromThisBoxX(); }; MinBoxY.Leave += (o, e) => UpdateFromMinBoxY(); @@ -59,8 +57,6 @@ public partial class TranslateForm : Form }; refForm = graph; - this.ableRaw = ableRaw; - this.ableTrans = ableTrans; double curX = 0, curY = 0; if (ableTrans is ITranslatableX transX) -- 2.49.0.windows.1 From 3665b0547aa4cd5e80e8d83ba95936684c25f9fc Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Sat, 23 Mar 2024 10:33:49 -0400 Subject: [PATCH 04/21] Nice progress. Some of it is bodged a bit but that's okay. --- Base/Forms/GraphForm.cs | 5 ++- Base/Graphable.cs | 2 +- Base/Graphables/ColumnTable.cs | 2 +- Base/Graphables/Equation.cs | 2 +- Base/Graphables/EquationDifference.cs | 58 +++++++++++++++++++++++++++ Base/Graphables/IntegralEquation.cs | 6 +-- Base/Graphables/SlopeField.cs | 2 +- Base/Graphables/TangentLine.cs | 4 +- Base/Parts/GraphUiCircle.cs | 2 +- Base/Parts/GraphUiText.cs | 38 ++++++++++++++++++ Testing/Program.cs | 13 ++---- 11 files changed, 113 insertions(+), 21 deletions(-) create mode 100644 Base/Graphables/EquationDifference.cs create mode 100644 Base/Parts/GraphUiText.cs diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index b266525..cc9b494 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -11,6 +11,7 @@ namespace Graphing.Forms; public partial class GraphForm : Form { + public static readonly Color BackgroundColor = Color.White; public static readonly Color MainAxisColor = Color.Black; public static readonly Color SemiAxisColor = Color.FromArgb(unchecked((int)0xFF_999999)); public static readonly Color QuarterAxisColor = Color.FromArgb(unchecked((int)0xFF_E0E0E0)); @@ -200,7 +201,7 @@ public partial class GraphForm : Form Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.HighQuality; - Brush background = new SolidBrush(Color.White); + Brush background = new SolidBrush(BackgroundColor); g.FillRectangle(background, e.ClipRectangle); PaintGrid(g); @@ -231,7 +232,7 @@ public partial class GraphForm : Form if (ables[i].ShouldSelectGraphable(this, graphMousePos, 2.5)) { Float2 selectedPoint = ables[i].GetSelectedPoint(this, graphMousePos); - GraphUiCircle select = new(selectedPoint, 8); + GraphUiCircle select = new(selectedPoint); Int2 textPos = GraphSpaceToScreenSpace(select.center); textPos.y -= (int)(DpiFloat * 32 / 192); diff --git a/Base/Graphable.cs b/Base/Graphable.cs index 2844ef7..e59f358 100644 --- a/Base/Graphable.cs +++ b/Base/Graphable.cs @@ -30,7 +30,7 @@ public abstract class Graphable public abstract IEnumerable GetItemsToRender(in GraphForm graph); - public abstract Graphable DeepCopy(); + public abstract Graphable ShallowCopy(); public virtual void EraseCache() { } public virtual long GetCacheBytes() => 0; diff --git a/Base/Graphables/ColumnTable.cs b/Base/Graphables/ColumnTable.cs index 0461cc3..55142d4 100644 --- a/Base/Graphables/ColumnTable.cs +++ b/Base/Graphables/ColumnTable.cs @@ -37,7 +37,7 @@ public class ColumnTable : Graphable public override long GetCacheBytes() => 16 * tableXY.Count; - public override Graphable DeepCopy() => new ColumnTable(width / 0.75, tableXY.ToArray().ToDictionary()); + public override Graphable ShallowCopy() => new ColumnTable(width / 0.75, tableXY); public override IEnumerable GetItemsToRender(in GraphForm graph) { diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index da7fa61..ef9c514 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -116,7 +116,7 @@ public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY } } - public override Graphable DeepCopy() => new Equation(equ); + public override Graphable ShallowCopy() => new Equation(equ); public override long GetCacheBytes() => cache.Count * 16; diff --git a/Base/Graphables/EquationDifference.cs b/Base/Graphables/EquationDifference.cs new file mode 100644 index 0000000..2f07d99 --- /dev/null +++ b/Base/Graphables/EquationDifference.cs @@ -0,0 +1,58 @@ +using Graphing.Abstract; +using Graphing.Forms; +using Graphing.Parts; +using System.Collections.Generic; + +namespace Graphing.Graphables; + +public class EquationDifference : Graphable, ITranslatableX, IEquationConvertible +{ + public double Position + { + get => _position; + set + { + _position = value; + points = new Float2(equA.GetValueAt(value), equB.GetValueAt(value)); + } + } + private double _position; + + public double OffsetX + { + get => Position; + set => Position = value; + } + + protected readonly Equation equA, equB; + protected Float2 points; // X represents equA.y, Y represents equB.y + + public EquationDifference(double position, Equation equA, Equation equB) + { + this.equA = equA; + this.equB = equB; + + Name = $"Difference between {equA.Name} and {equB.Name}"; + + Position = position; + } + + public override IEnumerable GetItemsToRender(in GraphForm graph) + { + Float2 pA = new(Position, points.x), + pB = new(Position, points.y), + pC = new(Position, (points.x + points.y) / 2); + return [new GraphUiText($"{points.x - points.y:0.00}", pC), + new GraphUiCircle(pA), new GraphUiCircle(pB), new GraphLine(pA, pB)]; + } + + public double DistanceAtPoint(double x) => equA.GetValueAt(x) - equB.GetValueAt(x); + + public override Graphable ShallowCopy() => new EquationDifference(Position, equA, equB); + + public Equation ToEquation() => new(DistanceAtPoint) + { + Color = Color, + Name = Name + }; +} diff --git a/Base/Graphables/IntegralEquation.cs b/Base/Graphables/IntegralEquation.cs index 51742fd..2279b0d 100644 --- a/Base/Graphables/IntegralEquation.cs +++ b/Base/Graphables/IntegralEquation.cs @@ -47,7 +47,7 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable usingAlt = true; } - public override Graphable DeepCopy() => new IntegralEquation(this); + public override Graphable ShallowCopy() => new IntegralEquation(this); public override IEnumerable GetItemsToRender(in GraphForm graph) { @@ -178,8 +178,8 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable public Graphable Derive() { - if (usingAlt) return altBaseEqu!.DeepCopy(); - else return (Equation)baseEqu!.DeepCopy(); + if (usingAlt) return altBaseEqu!.ShallowCopy(); + else return (Equation)baseEqu!.ShallowCopy(); } public Graphable Integrate() => new IntegralEquation(this); diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs index 066f658..d9a4549 100644 --- a/Base/Graphables/SlopeField.cs +++ b/Base/Graphables/SlopeField.cs @@ -72,7 +72,7 @@ public class SlopeField : Graphable return result; } - public override Graphable DeepCopy() => new SlopeField(detail, equ); + public override Graphable ShallowCopy() => new SlopeField(detail, equ); public override void EraseCache() => cache.Clear(); public override long GetCacheBytes() => cache.Count * 48; diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs index 9f5c2e1..5cc2da5 100644 --- a/Base/Graphables/TangentLine.cs +++ b/Base/Graphables/TangentLine.cs @@ -56,7 +56,7 @@ public class TangentLine : Graphable, IEquationConvertible, ITranslatableX public override IEnumerable GetItemsToRender(in GraphForm graph) { Float2 point = new(Position, currentSlope.y); - return [MakeSlopeLine(), new GraphUiCircle(point, 8)]; + return [MakeSlopeLine(), new GraphUiCircle(point)]; } protected GraphLine MakeSlopeLine() { @@ -81,7 +81,7 @@ public class TangentLine : Graphable, IEquationConvertible, ITranslatableX return result; } - public override Graphable DeepCopy() => new TangentLine(length, Position, parent); + public override Graphable ShallowCopy() => new TangentLine(length, Position, parent); public override void EraseCache() => slopeCache.Clear(); public override long GetCacheBytes() => slopeCache.Count * 24; diff --git a/Base/Parts/GraphUiCircle.cs b/Base/Parts/GraphUiCircle.cs index 7f46411..8221a46 100644 --- a/Base/Parts/GraphUiCircle.cs +++ b/Base/Parts/GraphUiCircle.cs @@ -13,7 +13,7 @@ public record struct GraphUiCircle : IGraphPart center = new(); radius = 1; } - public GraphUiCircle(Float2 center, int radius) + public GraphUiCircle(Float2 center, int radius = 8) { this.center = center; this.radius = radius; diff --git a/Base/Parts/GraphUiText.cs b/Base/Parts/GraphUiText.cs new file mode 100644 index 0000000..0d27573 --- /dev/null +++ b/Base/Parts/GraphUiText.cs @@ -0,0 +1,38 @@ +using Graphing.Forms; +using System.Drawing; + +namespace Graphing.Parts; + +public record struct GraphUiText : IGraphPart +{ + public string text; + public Float2 position; + public bool background; + + private readonly Font font; + private readonly Brush? backgroundBrush; + + public GraphUiText(string text, Float2 position, bool background = true) + { + font = new Font("Segoe UI", 8, FontStyle.Bold); + + this.text = text; + this.position = position; + this.background = background; + + if (background) backgroundBrush = new SolidBrush(GraphForm.BackgroundColor); + } + + public readonly void Render(in GraphForm form, in Graphics g, in Pen p) + { + Int2 posScreen = form.GraphSpaceToScreenSpace(position); + posScreen.y -= (int)(form.DpiFloat * font.Size * 2 / 192); + if (background) + { + SizeF size = g.MeasureString(text, font); + g.FillRectangle(backgroundBrush!, new Rectangle(posScreen.x, posScreen.y, + (int)size.Width, (int)size.Height)); + } + g.DrawString(text, font, p.Brush, new Point(posScreen.x, posScreen.y)); + } +} diff --git a/Testing/Program.cs b/Testing/Program.cs index 851e7f8..2c58989 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -16,15 +16,10 @@ internal static class Program GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - Equation equ = new(Math.Sin); - SlopeField sf = new(2, (x, y) => Math.Cos(x)); - TangentLine tl = new(2, 2, equ); - graph.Graph(equ, sf, tl); - - // Now, when integrating equations, the result is much less jagged - // and much faster. Try it out! You can also select points along - // equations and such as well. Click on an equation to see for - // yourself! + Equation equA = new(Math.Sin), + equB = new(Math.Cos); + EquationDifference diff = new(2, equA, equB); + graph.Graph(equA, equB, diff); Application.Run(graph); } -- 2.49.0.windows.1 From 4fc2425797c8509275991e25373cff6392a4502e Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Mon, 25 Mar 2024 08:50:08 -0400 Subject: [PATCH 05/21] Some updater progress. I want to make it automatic at some point. --- Base/Forms/GraphForm.cs | 58 ++++++++++++++++++++++++++++++++++++++++- Testing/Program.cs | 2 +- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index cc9b494..e71004d 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -2,9 +2,15 @@ using Graphing.Parts; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading.Tasks; using System.Windows.Forms; namespace Graphing.Forms; @@ -72,6 +78,8 @@ public partial class GraphForm : Form ZoomLevel = 1; initialWindowPos = Location; initialWindowSize = Size; + + RunUpdateChecker(); } public Int2 GraphSpaceToScreenSpace(Float2 graphPoint) @@ -475,7 +483,7 @@ public partial class GraphForm : Form cacheForm.TopMost = true; cacheForm.Show(); } - private void MiscMenuPreload_Click(object sender, EventArgs e) + private void MiscMenuPreload_Click(object? sender, EventArgs e) { Float2 min = MinVisibleGraph, max = MaxVisibleGraph; Float2 add = new(max.x - min.x, max.y - min.y); @@ -507,4 +515,52 @@ public partial class GraphForm : Form } shifter.Show(); } + + private async void RunUpdateChecker() + { + try + { + HttpClient http = new(); + HttpRequestMessage request = new(HttpMethod.Get, "https://api.github.com/repos/That-One-Nerd/Graphing/releases"); + request.Headers.Add("User-Agent", "ThatOneNerd.Graphing-Update-Checker"); + + HttpResponseMessage result = await http.SendAsync(request); + if (!result.IsSuccessStatusCode) + { + Console.WriteLine($"Failed to check for updates."); + return; + } + + JsonArray arr = JsonSerializer.Deserialize(await result.Content.ReadAsStreamAsync())!; + JsonObject latest = arr[0]!.AsObject(); + + Version curVersion = Version.Parse(Assembly.GetAssembly(typeof(GraphForm))!.FullName!.Split(',')[1].Trim()[8..^2]); + Version newVersion = Version.Parse(latest["tag_name"]!.GetValue()); + + if (newVersion > curVersion) + { + // Updates are required. + DialogResult button = MessageBox.Show( + $"A new update is available!\n{curVersion} -> {newVersion}\nWould you like to download the update?", + "Graphing Calculator Update", MessageBoxButtons.YesNo); + + if (button == DialogResult.No) return; + + ProcessStartInfo website = new() + { + FileName = latest["html_url"]!.GetValue(), + UseShellExecute = true + }; + Process.Start(website); + } + else + { + Console.WriteLine($"Up-to-date."); + } + } + catch (Exception ex) + { + Console.WriteLine($"Failed to check for updates:\n{ex}"); + } + } } diff --git a/Testing/Program.cs b/Testing/Program.cs index 2c58989..2e256f7 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -13,7 +13,7 @@ internal static class Program Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); - + GraphForm graph = new("One Of The Graphing Calculators Of All Time"); Equation equA = new(Math.Sin), -- 2.49.0.windows.1 From d19c7a3fc3a9cdb9d007a95bd7e46ad3e5a7f106 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Mon, 1 Apr 2024 09:41:04 -0400 Subject: [PATCH 06/21] Made a little progress with the update checker. Don't really feel like working on it right now. --- Base/Forms/GraphForm.cs | 48 +++++++++++++++++--- Base/Helpers/UpdaterHelper.cs | 83 +++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 Base/Helpers/UpdaterHelper.cs diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index e71004d..c6227f5 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -1,4 +1,5 @@ using Graphing.Abstract; +using Graphing.Helpers; using Graphing.Parts; using System; using System.Collections.Generic; @@ -516,7 +517,7 @@ public partial class GraphForm : Form shifter.Show(); } - private async void RunUpdateChecker() + private static async void RunUpdateChecker() { try { @@ -537,6 +538,8 @@ public partial class GraphForm : Form Version curVersion = Version.Parse(Assembly.GetAssembly(typeof(GraphForm))!.FullName!.Split(',')[1].Trim()[8..^2]); Version newVersion = Version.Parse(latest["tag_name"]!.GetValue()); + curVersion = Version.Parse("1.0.0"); + if (newVersion > curVersion) { // Updates are required. @@ -546,12 +549,45 @@ public partial class GraphForm : Form if (button == DialogResult.No) return; - ProcessStartInfo website = new() + string? project = UpdaterHelper.GetProjectPath(); + if (project is null) { - FileName = latest["html_url"]!.GetValue(), - UseShellExecute = true - }; - Process.Start(website); + MessageBox.Show("Cannot find project root. You'll likely have to update manually.", + "Error running automatic updates.", MessageBoxButtons.OK, + MessageBoxIcon.Error); + return; + } + + if (await UpdaterHelper.UsingNugetPackage(project)) + { + Console.WriteLine($"Attempting to update via the NuGet Package Manager."); + bool status = await UpdaterHelper.UpdateProjectByNuget(latest["tag_name"]!.GetValue(), project); + if (!status) + { + MessageBox.Show("Failed to update with the NuGet Package Manager. " + + "You'll likely have to update manually.", + "Error running automatic updates.", MessageBoxButtons.OK, + MessageBoxIcon.Error); + return; + } + } + else + { + Console.WriteLine($"Attempting to update via a GitHub asset download."); + bool status = await UpdaterHelper.UpdateProjectByGitHub(latest["tag_name"]!.GetValue(), project); + if (status) + { + MessageBox.Show("Update ready. Restart the project to complete the update."); + } + else + { + MessageBox.Show("Failed to update with an automatic download. " + + "You'll likely have to update manually.", + "Error running automatic updates.", MessageBoxButtons.OK, + MessageBoxIcon.Error); + return; + } + } } else { diff --git a/Base/Helpers/UpdaterHelper.cs b/Base/Helpers/UpdaterHelper.cs new file mode 100644 index 0000000..b8c6fda --- /dev/null +++ b/Base/Helpers/UpdaterHelper.cs @@ -0,0 +1,83 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Graphing.Helpers; + +internal static class UpdaterHelper +{ + public static async Task UsingNugetPackage(string? project = null) + { + project ??= GetProjectPath(); + + Process packageListProc = new(); + packageListProc.StartInfo.FileName = "dotnet"; + packageListProc.StartInfo.Arguments = "list package"; + packageListProc.StartInfo.WorkingDirectory = Path.GetDirectoryName(project); + + packageListProc.StartInfo.UseShellExecute = false; + packageListProc.StartInfo.RedirectStandardOutput = true; + + StringBuilder contentBuilder = new(); + packageListProc.OutputDataReceived += (o, e) => contentBuilder.AppendLine(e.Data); + + packageListProc.Start(); + packageListProc.BeginOutputReadLine(); + await packageListProc.WaitForExitAsync(); + + string content = contentBuilder.ToString(); + return content.Contains($"ThatOneNerd.Graphing"); + } + public static async Task UpdateProjectByNuget(string version, string? project = null) + { + project ??= GetProjectPath(); + + Process updateProc = new(); + updateProc.StartInfo.FileName = "dotnet"; + updateProc.StartInfo.Arguments = $"add package ThatOneNerd.Graphing --version {version}"; + updateProc.StartInfo.WorkingDirectory = Path.GetDirectoryName(project); + + updateProc.StartInfo.UseShellExecute = false; + updateProc.StartInfo.RedirectStandardError = true; + + StringBuilder errorBuilder = new(); + updateProc.ErrorDataReceived += (o, e) => errorBuilder.AppendLine(e.Data); + + updateProc.Start(); + updateProc.BeginErrorReadLine(); + await updateProc.WaitForExitAsync(); + + // Could be shrunk but it makes it less clear. If there's any data written to + // the error stream, it did not succeed. + if (errorBuilder.Length > 0) return false; // Error. + else return true; // Success. + } + public static async Task UpdateProjectByGitHub(string version, string? project = null) + { + // TODO + return false; + } + + public static string? GetProjectPath() + { + Assembly? entryAsm = Assembly.GetEntryAssembly(); + if (entryAsm is null) return null; + + string? directory = Path.GetDirectoryName(entryAsm.Location); + while (directory is not null) + { + string[] files = Directory.GetFiles(directory); + string? project = files.FirstOrDefault(x => x.EndsWith(".csproj") || + x.EndsWith(".sln")); + if (project is not null) return project; + + directory = Path.GetDirectoryName(directory); + } + + return null; + } +} -- 2.49.0.windows.1 From e5c985c060d44bec0504471d353374ec450247fe Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Tue, 2 Apr 2024 09:37:45 -0400 Subject: [PATCH 07/21] Improved conversions. You choose whether to remove the original. --- ...tionConvertible.cs => IConvertEquation.cs} | 4 ++- Base/Abstract/IConvertSlopeField.cs | 10 +++++++ Base/Forms/GraphForm.Designer.cs | 26 ++++++++++++------- Base/Forms/GraphForm.cs | 21 ++++++++++++--- Base/Graphables/Equation.cs | 16 +++++++++--- Base/Graphables/EquationDifference.cs | 4 ++- Base/Graphables/TangentLine.cs | 4 ++- 7 files changed, 66 insertions(+), 19 deletions(-) rename Base/Abstract/{IEquationConvertible.cs => IConvertEquation.cs} (52%) create mode 100644 Base/Abstract/IConvertSlopeField.cs diff --git a/Base/Abstract/IEquationConvertible.cs b/Base/Abstract/IConvertEquation.cs similarity index 52% rename from Base/Abstract/IEquationConvertible.cs rename to Base/Abstract/IConvertEquation.cs index 11bcfc5..da4f11a 100644 --- a/Base/Abstract/IEquationConvertible.cs +++ b/Base/Abstract/IConvertEquation.cs @@ -2,7 +2,9 @@ namespace Graphing.Abstract; -public interface IEquationConvertible +public interface IConvertEquation { + public bool UngraphWhenConvertedToEquation { get; } + public Equation ToEquation(); } diff --git a/Base/Abstract/IConvertSlopeField.cs b/Base/Abstract/IConvertSlopeField.cs new file mode 100644 index 0000000..2c3982a --- /dev/null +++ b/Base/Abstract/IConvertSlopeField.cs @@ -0,0 +1,10 @@ +using Graphing.Graphables; + +namespace Graphing.Abstract; + +public interface IConvertSlopeField +{ + public bool UngraphWhenConvertedToSlopeField { get; } + + public SlopeField ToSlopeField(int detail); +} diff --git a/Base/Forms/GraphForm.Designer.cs b/Base/Forms/GraphForm.Designer.cs index 6ad42fb..722588b 100644 --- a/Base/Forms/GraphForm.Designer.cs +++ b/Base/Forms/GraphForm.Designer.cs @@ -44,12 +44,13 @@ namespace Graphing.Forms MenuOperations = new ToolStripMenuItem(); MenuOperationsDerivative = new ToolStripMenuItem(); MenuOperationsIntegral = new ToolStripMenuItem(); + MenuOperationsTranslate = new ToolStripMenuItem(); MenuConvert = new ToolStripMenuItem(); MenuConvertEquation = new ToolStripMenuItem(); MenuMisc = new ToolStripMenuItem(); MenuMiscCaches = new ToolStripMenuItem(); MiscMenuPreload = new ToolStripMenuItem(); - MenuOperationsTranslate = new ToolStripMenuItem(); + MenuConvertSlopeField = new ToolStripMenuItem(); GraphMenu.SuspendLayout(); SuspendLayout(); // @@ -121,13 +122,13 @@ namespace Graphing.Forms // MenuElementsColors // MenuElementsColors.Name = "MenuElementsColors"; - MenuElementsColors.Size = new Size(359, 44); + MenuElementsColors.Size = new Size(233, 44); MenuElementsColors.Text = "Colors"; // // MenuElementsRemove // MenuElementsRemove.Name = "MenuElementsRemove"; - MenuElementsRemove.Size = new Size(359, 44); + MenuElementsRemove.Size = new Size(233, 44); MenuElementsRemove.Text = "Remove"; // // MenuOperations @@ -149,9 +150,15 @@ namespace Graphing.Forms MenuOperationsIntegral.Size = new Size(360, 44); MenuOperationsIntegral.Text = "Compute Integral"; // + // MenuOperationsTranslate + // + MenuOperationsTranslate.Name = "MenuOperationsTranslate"; + MenuOperationsTranslate.Size = new Size(360, 44); + MenuOperationsTranslate.Text = "Translate"; + // // MenuConvert // - MenuConvert.DropDownItems.AddRange(new ToolStripItem[] { MenuConvertEquation }); + MenuConvert.DropDownItems.AddRange(new ToolStripItem[] { MenuConvertEquation, MenuConvertSlopeField }); MenuConvert.Name = "MenuConvert"; MenuConvert.Size = new Size(118, 38); MenuConvert.Text = "Convert"; @@ -159,7 +166,7 @@ namespace Graphing.Forms // MenuConvertEquation // MenuConvertEquation.Name = "MenuConvertEquation"; - MenuConvertEquation.Size = new Size(273, 44); + MenuConvertEquation.Size = new Size(359, 44); MenuConvertEquation.Text = "To Equation"; // // MenuMisc @@ -183,11 +190,11 @@ namespace Graphing.Forms MiscMenuPreload.Text = "Preload Cache"; MiscMenuPreload.Click += MiscMenuPreload_Click; // - // MenuOperationsTranslate + // MenuConvertSlopeField // - MenuOperationsTranslate.Name = "MenuOperationsTranslate"; - MenuOperationsTranslate.Size = new Size(360, 44); - MenuOperationsTranslate.Text = "Translate"; + MenuConvertSlopeField.Name = "MenuConvertSlopeField"; + MenuConvertSlopeField.Size = new Size(359, 44); + MenuConvertSlopeField.Text = "To Slope Field"; // // GraphForm // @@ -226,5 +233,6 @@ namespace Graphing.Forms private ToolStripMenuItem MenuElementsColors; private ToolStripMenuItem MenuElementsRemove; private ToolStripMenuItem MenuOperationsTranslate; + private ToolStripMenuItem MenuConvertSlopeField; } } \ No newline at end of file diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index c6227f5..c4bd78d 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -369,6 +369,7 @@ public partial class GraphForm : Form MenuOperationsDerivative.DropDownItems.Clear(); MenuOperationsIntegral.DropDownItems.Clear(); MenuConvertEquation.DropDownItems.Clear(); + MenuConvertSlopeField.DropDownItems.Clear(); MenuOperationsTranslate.DropDownItems.Clear(); foreach (Graphable able in ables) @@ -409,7 +410,7 @@ public partial class GraphForm : Form integralItem.Click += (o, e) => Graph(integrable.Integrate()); MenuOperationsIntegral.DropDownItems.Add(integralItem); } - if (able is IEquationConvertible equConvert) + if (able is IConvertEquation equConvert) { ToolStripMenuItem equItem = new() { @@ -418,11 +419,25 @@ public partial class GraphForm : Form }; equItem.Click += (o, e) => { - Ungraph(able); + if (equConvert.UngraphWhenConvertedToEquation) Ungraph(able); Graph(equConvert.ToEquation()); }; MenuConvertEquation.DropDownItems.Add(equItem); } + if (able is IConvertSlopeField sfConvert) + { + ToolStripMenuItem sfItem = new() + { + ForeColor = able.Color, + Text = able.Name + }; + sfItem.Click += (o, e) => + { + if (sfConvert.UngraphWhenConvertedToSlopeField) Ungraph(able); + Graph(sfConvert.ToSlopeField(2)); + }; + MenuConvertSlopeField.DropDownItems.Add(sfItem); + } if (able is ITranslatable translatable) { ToolStripMenuItem transItem = new() @@ -538,8 +553,6 @@ public partial class GraphForm : Form Version curVersion = Version.Parse(Assembly.GetAssembly(typeof(GraphForm))!.FullName!.Split(',')[1].Trim()[8..^2]); Version newVersion = Version.Parse(latest["tag_name"]!.GetValue()); - curVersion = Version.Parse("1.0.0"); - if (newVersion > curVersion) { // Updates are required. diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index ef9c514..cf0d6aa 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -6,10 +6,12 @@ using System.Collections.Generic; namespace Graphing.Graphables; -public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY +public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, IConvertSlopeField { private static int equationNum; + public bool UngraphWhenConvertedToSlopeField => false; + public double OffsetX { get; set; } public double OffsetY { get; set; } @@ -60,15 +62,23 @@ public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY return lines; } - public Graphable Derive() => new Equation(x => + protected double DerivativeAtPoint(double x) { const double step = 1e-3; return (equ(x + step) - equ(x)) / step; - }); + } + + public Graphable Derive() => new Equation(DerivativeAtPoint); public Graphable Integrate() => new IntegralEquation(this); public EquationDelegate GetDelegate() => equ; + public SlopeField ToSlopeField(int detail) => new(detail, (x, y) => DerivativeAtPoint(x)) + { + Color = Color, + Name = $"Slope Field of {Name}" + }; + public override void EraseCache() => cache.Clear(); protected double GetFromCache(double x, double epsilon) { diff --git a/Base/Graphables/EquationDifference.cs b/Base/Graphables/EquationDifference.cs index 2f07d99..bdd341c 100644 --- a/Base/Graphables/EquationDifference.cs +++ b/Base/Graphables/EquationDifference.cs @@ -5,8 +5,10 @@ using System.Collections.Generic; namespace Graphing.Graphables; -public class EquationDifference : Graphable, ITranslatableX, IEquationConvertible +public class EquationDifference : Graphable, ITranslatableX, IConvertEquation { + public bool UngraphWhenConvertedToEquation => true; + public double Position { get => _position; diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs index 5cc2da5..ab19db4 100644 --- a/Base/Graphables/TangentLine.cs +++ b/Base/Graphables/TangentLine.cs @@ -6,8 +6,10 @@ using System.Collections.Generic; namespace Graphing.Graphables; -public class TangentLine : Graphable, IEquationConvertible, ITranslatableX +public class TangentLine : Graphable, IConvertEquation, ITranslatableX { + public bool UngraphWhenConvertedToEquation => true; + public double Position { get => _position; -- 2.49.0.windows.1 From 3b6ebc7b9977e0b2f8afa3a1e3bacd03260cc3b1 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Tue, 2 Apr 2024 09:53:42 -0400 Subject: [PATCH 08/21] Some quick parametrics. See how fast it is to homebrew this? I know I haven't completed a bunch of stuff but I'll get to it soon. --- Base/Graphables/ParametricEquation.cs | 63 +++++++++++++++++++++++++++ Testing/Program.cs | 3 +- 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 Base/Graphables/ParametricEquation.cs diff --git a/Base/Graphables/ParametricEquation.cs b/Base/Graphables/ParametricEquation.cs new file mode 100644 index 0000000..0d92743 --- /dev/null +++ b/Base/Graphables/ParametricEquation.cs @@ -0,0 +1,63 @@ +using Graphing.Abstract; +using Graphing.Forms; +using Graphing.Parts; +using System; +using System.Collections.Generic; + +namespace Graphing.Graphables; + +public class ParametricEquation : Graphable, ITranslatableXY +{ + private static int equationNum; + + public double OffsetX { get; set; } + public double OffsetY { get; set; } + + public double InitialT { get; set; } + public double FinalT { get; set; } + + protected readonly ParametricDelegate equX, equY; + + public ParametricEquation(double initialT, double finalT, + ParametricDelegate equX, ParametricDelegate equY) + { + equationNum++; + Name = $"Parametric Equation {equationNum}"; + + InitialT = initialT; + FinalT = finalT; + + this.equX = equX; + this.equY = equY; + } + + public override Graphable ShallowCopy() => new ParametricEquation(InitialT, FinalT, equX, equY); + + public override IEnumerable GetItemsToRender(in GraphForm graph) + { + const int step = 10; + + double epsilon = Math.Abs(graph.ScreenSpaceToGraphSpace(new Int2(0, 0)).x + - graph.ScreenSpaceToGraphSpace(new Int2(step / 2, 0)).x) / 5; + epsilon *= graph.DpiFloat / 192; + + List lines = []; + + Float2 previousPoint = GetPointAt(InitialT); + for (double t = InitialT; t <= FinalT; t += epsilon) + { + Float2 currentPoint = GetPointAt(t); + lines.Add(new GraphLine(previousPoint, currentPoint)); + previousPoint = currentPoint; + } + + return lines; + } + + public Float2 GetPointAt(double t) + { + return new(equX(t) + OffsetX, equY(t) + OffsetY); + } +} + +public delegate double ParametricDelegate(double t); diff --git a/Testing/Program.cs b/Testing/Program.cs index 2e256f7..333cacb 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -19,7 +19,8 @@ internal static class Program Equation equA = new(Math.Sin), equB = new(Math.Cos); EquationDifference diff = new(2, equA, equB); - graph.Graph(equA, equB, diff); + ParametricEquation equC = new(0, 4, t => t * t - 1, t => Math.Sqrt(t) + t + 1); + graph.Graph(equA, equB, diff, equC); Application.Run(graph); } -- 2.49.0.windows.1 From 470a70a97ae4e81c0f25c4dad37a20145456b3b0 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Wed, 3 Apr 2024 12:27:53 -0400 Subject: [PATCH 09/21] Some stuff. Satisfied for now with the slope-field generation and the parametric equations. --- Base/Abstract/IConvertColumnTable.cs | 10 +++ Base/Forms/GraphForm.cs | 11 +++- Base/Forms/TranslateForm.cs | 5 +- Base/Graphables/ColumnTable.cs | 3 +- Base/Graphables/Equation.cs | 8 ++- Base/Graphables/IntegralEquation.cs | 2 +- Base/Graphables/ParametricEquation.cs | 87 ++++++++++++++++++++++++--- Base/Parts/GraphRectangle.cs | 13 ++-- Testing/Program.cs | 4 +- 9 files changed, 121 insertions(+), 22 deletions(-) create mode 100644 Base/Abstract/IConvertColumnTable.cs diff --git a/Base/Abstract/IConvertColumnTable.cs b/Base/Abstract/IConvertColumnTable.cs new file mode 100644 index 0000000..4b87aaf --- /dev/null +++ b/Base/Abstract/IConvertColumnTable.cs @@ -0,0 +1,10 @@ +using Graphing.Graphables; + +namespace Graphing.Abstract; + +public interface IConvertColumnTable +{ + public bool UngraphWhenConvertedToColumnTable { get; } + + public ColumnTable ToColumnTable(double start, double end, int detail); +} diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index c4bd78d..8d20d04 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -3,7 +3,6 @@ using Graphing.Helpers; using Graphing.Parts; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; @@ -11,7 +10,6 @@ using System.Net.Http; using System.Reflection; using System.Text.Json; using System.Text.Json.Nodes; -using System.Threading.Tasks; using System.Windows.Forms; namespace Graphing.Forms; @@ -278,6 +276,13 @@ public partial class GraphForm : Form Invalidate(false); } + public bool IsGraphPointVisible(Float2 point) + { + Int2 pixelPos = GraphSpaceToScreenSpace(point); + return pixelPos.x >= 0 && pixelPos.x < ClientRectangle.Width && + pixelPos.y >= 0 && pixelPos.y < ClientRectangle.Height; + } + private bool mouseDrag = false; private Int2 initialMouseLocation; private Float2 initialScreenCenter; @@ -371,6 +376,8 @@ public partial class GraphForm : Form MenuConvertEquation.DropDownItems.Clear(); MenuConvertSlopeField.DropDownItems.Clear(); MenuOperationsTranslate.DropDownItems.Clear(); + // At some point, we'll have a Convert To Column Table button, + // but I'll need to make a form for the ranges when I do that. foreach (Graphable able in ables) { diff --git a/Base/Forms/TranslateForm.cs b/Base/Forms/TranslateForm.cs index fdd2240..d0ac022 100644 --- a/Base/Forms/TranslateForm.cs +++ b/Base/Forms/TranslateForm.cs @@ -100,6 +100,8 @@ public partial class TranslateForm : Form TitleLabel.Text = $"There doesn't seem to be anything you can translate for {ableRaw.Name}."; } + // TODO: Maybe replace these default limits with what's visible on screen? + // Tried it and it got a bit confusing so maybe not. minX = -10; maxX = 10; minY = -10; @@ -272,11 +274,10 @@ public partial class TranslateForm : Form private static double Lerp(double a, double b, double t) => a + t * (b - a); private static double InverseLerp(double a, double b, double c) => (c - a) / (b - a); - private void TrackX_Scroll(object? sender, EventArgs e) + private void TrackX_Scroll(object sender, EventArgs e) { UpdateFromSliderX(true); } - private void TrackY_Scroll(object sender, EventArgs e) { UpdateFromSliderY(true); diff --git a/Base/Graphables/ColumnTable.cs b/Base/Graphables/ColumnTable.cs index 55142d4..7b95593 100644 --- a/Base/Graphables/ColumnTable.cs +++ b/Base/Graphables/ColumnTable.cs @@ -23,6 +23,7 @@ public class ColumnTable : Graphable } public ColumnTable(double step, Equation equation, double min, double max) { + Color = equation.Color; Name = $"Column Table for {equation.Name}"; tableXY = []; @@ -45,7 +46,7 @@ public class ColumnTable : Graphable foreach (KeyValuePair col in tableXY) { items.Add(GraphRectangle.FromSize(new Float2(col.Key, col.Value / 2), - new Float2(width, col.Value))); + new Float2(width, col.Value), 0.625)); } return items; diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index cf0d6aa..a118299 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -6,10 +6,12 @@ using System.Collections.Generic; namespace Graphing.Graphables; -public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, IConvertSlopeField +public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, IConvertSlopeField, + IConvertColumnTable { private static int equationNum; + public bool UngraphWhenConvertedToColumnTable => false; public bool UngraphWhenConvertedToSlopeField => false; public double OffsetX { get; set; } @@ -65,7 +67,7 @@ public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, ICo protected double DerivativeAtPoint(double x) { const double step = 1e-3; - return (equ(x + step) - equ(x)) / step; + return (equ(x + step - OffsetX) - equ(x - OffsetX)) / step; } public Graphable Derive() => new Equation(DerivativeAtPoint); @@ -78,6 +80,8 @@ public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, ICo Color = Color, Name = $"Slope Field of {Name}" }; + public ColumnTable ToColumnTable(double start, double end, int detail) + => new(1.0 / detail, this, start, end); public override void EraseCache() => cache.Clear(); protected double GetFromCache(double x, double epsilon) diff --git a/Base/Graphables/IntegralEquation.cs b/Base/Graphables/IntegralEquation.cs index 2279b0d..8c49140 100644 --- a/Base/Graphables/IntegralEquation.cs +++ b/Base/Graphables/IntegralEquation.cs @@ -164,7 +164,7 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable } else { - stepY += baseEquDel!(stepX) * dX; + stepY += (baseEquDel!(stepX - baseEqu!.OffsetX) + baseEqu.OffsetY) * dX; } } diff --git a/Base/Graphables/ParametricEquation.cs b/Base/Graphables/ParametricEquation.cs index 0d92743..24f7f78 100644 --- a/Base/Graphables/ParametricEquation.cs +++ b/Base/Graphables/ParametricEquation.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace Graphing.Graphables; -public class ParametricEquation : Graphable, ITranslatableXY +public class ParametricEquation : Graphable, IDerivable, ITranslatableXY { private static int equationNum; @@ -17,6 +17,7 @@ public class ParametricEquation : Graphable, ITranslatableXY public double FinalT { get; set; } protected readonly ParametricDelegate equX, equY; + protected readonly List<(double t, Float2 point)> cache; public ParametricEquation(double initialT, double finalT, ParametricDelegate equX, ParametricDelegate equY) @@ -29,6 +30,7 @@ public class ParametricEquation : Graphable, ITranslatableXY this.equX = equX; this.equY = equY; + cache = []; } public override Graphable ShallowCopy() => new ParametricEquation(InitialT, FinalT, equX, equY); @@ -38,25 +40,94 @@ public class ParametricEquation : Graphable, ITranslatableXY const int step = 10; double epsilon = Math.Abs(graph.ScreenSpaceToGraphSpace(new Int2(0, 0)).x - - graph.ScreenSpaceToGraphSpace(new Int2(step / 2, 0)).x) / 5; - epsilon *= graph.DpiFloat / 192; + - graph.ScreenSpaceToGraphSpace(new Int2(step, 0)).x); List lines = []; - Float2 previousPoint = GetPointAt(InitialT); + Float2 previousPoint = GetFromCache(InitialT, epsilon); for (double t = InitialT; t <= FinalT; t += epsilon) { - Float2 currentPoint = GetPointAt(t); - lines.Add(new GraphLine(previousPoint, currentPoint)); + Float2 currentPoint = GetFromCache(t, epsilon); + if (graph.IsGraphPointVisible(currentPoint) || + graph.IsGraphPointVisible(previousPoint)) + lines.Add(new GraphLine(previousPoint, currentPoint)); previousPoint = currentPoint; } return lines; } - public Float2 GetPointAt(double t) + public Graphable Derive() => + new ParametricEquation(InitialT, FinalT, GetDerivativeAtPointX, GetDerivativeAtPointY); + + public ParametricDelegate GetXDelegate() => equX; + public ParametricDelegate GetYDelegate() => equY; + + public double GetDerivativeAtPointX(double t) { - return new(equX(t) + OffsetX, equY(t) + OffsetY); + const double step = 1e-3; + return (equX(t + step) - equX(t)) / step; + } + public double GetDerivativeAtPointY(double t) + { + const double step = 1e-3; + return (equY(t + step) - equY(t)) / step; + } + public Float2 GetDerivativeAtPoint(double t) => + new(GetDerivativeAtPointX(t), GetDerivativeAtPointY(t)); + + public Float2 GetPointAt(double t) => GetFromCache(t, 0); + + public override void EraseCache() => cache.Clear(); + protected Float2 GetFromCache(double t, double epsilon) + { + (double dist, Float2 nearest, int index) = NearestCachedPoint(t); + if (dist < epsilon) return new(nearest.x + OffsetX, nearest.y + OffsetY); + else + { + Float2 result = new(equX(t), equY(t)); + cache.Insert(index + 1, (t, result)); + return new(result.x + OffsetX, result.y + OffsetY); + } + } + public override long GetCacheBytes() => cache.Count * 24; + + protected (double dist, Float2 point, int index) NearestCachedPoint(double t) + { + if (cache.Count <= 1) return (double.PositiveInfinity, new(double.NaN, double.NaN), -1); + else if (cache.Count == 1) + { + (double resultT, Float2 resultPoint) = cache[0]; + return (Math.Abs(resultT - t), resultPoint, 0); + } + else + { + int boundA = 0, boundB = cache.Count; + do + { + int boundC = (boundA + boundB) / 2; + + (double thisT, Float2 thisPoint) = cache[boundC]; + if (thisT == t) return (0, thisPoint, boundC); + else if (thisT > t) + { + boundA = boundC; + } + else // thisT < t + { + boundB = boundC; + } + + } while (boundB - boundA > 1); + + (double resultT, Float2 resultPoint) = cache[boundA]; + return (Math.Abs(resultT - t), resultPoint, boundA); + } + } + + public override void Preload(Float2 xRange, Float2 yRange, double step) + { + for (double t = InitialT; t <= FinalT; t += step) GetFromCache(t, step); } } diff --git a/Base/Parts/GraphRectangle.cs b/Base/Parts/GraphRectangle.cs index 4881397..161d7e2 100644 --- a/Base/Parts/GraphRectangle.cs +++ b/Base/Parts/GraphRectangle.cs @@ -6,24 +6,28 @@ namespace Graphing.Parts; public record struct GraphRectangle : IGraphPart { public Float2 min, max; + public double opacity; public GraphRectangle() { min = new(); max = new(); + opacity = 1; } - public static GraphRectangle FromSize(Float2 center, Float2 size) => new() + public static GraphRectangle FromSize(Float2 center, Float2 size, double opacity = 1) => new() { min = new(center.x - size.x / 2, center.y - size.y / 2), max = new(center.x + size.x / 2, - center.y + size.y / 2) + center.y + size.y / 2), + opacity = opacity, }; - public static GraphRectangle FromRange(Float2 min, Float2 max) => new() + public static GraphRectangle FromRange(Float2 min, Float2 max, double opacity = 1) => new() { min = min, - max = max + max = max, + opacity = opacity, }; public void Render(in GraphForm form, in Graphics g, in Pen pen) @@ -41,6 +45,7 @@ public record struct GraphRectangle : IGraphPart start.y - end.y); if (size.x == 0 || size.y == 0) return; + pen.Color = Color.FromArgb((int)(opacity * 255), pen.Color); g.FillRectangle(pen.Brush, new Rectangle(start.x, end.y, size.x, size.y)); } } diff --git a/Testing/Program.cs b/Testing/Program.cs index 333cacb..ef0bc3f 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -19,8 +19,8 @@ internal static class Program Equation equA = new(Math.Sin), equB = new(Math.Cos); EquationDifference diff = new(2, equA, equB); - ParametricEquation equC = new(0, 4, t => t * t - 1, t => Math.Sqrt(t) + t + 1); - graph.Graph(equA, equB, diff, equC); + ParametricEquation equC = new(0, 20, t => 0.0375 * t * Math.Cos(t), t => 0.0625 * t * Math.Sin(t) + 3); + graph.Graph(equA, equB, diff, equC, equA.ToColumnTable(-1, 1, 2)); Application.Run(graph); } -- 2.49.0.windows.1 From 41732d01e7638aff0826806f740d992df6383276 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Mon, 8 Apr 2024 09:15:01 -0400 Subject: [PATCH 10/21] Added UI selection parts. Now the text is more useful. --- Base/Forms/GraphForm.cs | 15 +----- Base/Forms/ViewCacheForm.cs | 1 + Base/Graphable.cs | 2 +- Base/Graphables/ColumnTable.cs | 76 +++++++++++++++++++++++++++ Base/Graphables/Equation.cs | 12 ++++- Base/Graphables/EquationDifference.cs | 44 ++++++++++++++-- Base/Graphables/IntegralEquation.cs | 4 +- Base/Graphables/SlopeField.cs | 9 +++- Base/Graphables/TangentLine.cs | 19 +++++-- Base/Parts/GraphRectangle.cs | 2 + Base/Parts/GraphUiText.cs | 55 +++++++++++++++++-- Testing/Program.cs | 3 +- 12 files changed, 211 insertions(+), 31 deletions(-) diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 8d20d04..8f75202 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -238,19 +238,8 @@ public partial class GraphForm : Form { if (ables[i].ShouldSelectGraphable(this, graphMousePos, 2.5)) { - Float2 selectedPoint = ables[i].GetSelectedPoint(this, graphMousePos); - GraphUiCircle select = new(selectedPoint); - - Int2 textPos = GraphSpaceToScreenSpace(select.center); - textPos.y -= (int)(DpiFloat * 32 / 192); - - string content = $"({selectedPoint.x:0.00}, {selectedPoint.y:0.00})"; - - SizeF textSize = g.MeasureString(content, textFont); - g.FillRectangle(background, new Rectangle(textPos.x, textPos.y, - (int)textSize.Width, (int)textSize.Height)); - g.DrawString(content, textFont, graphPens[i].Brush, new Point(textPos.x, textPos.y)); - select.Render(this, g, graphPens[i]); + IEnumerable selectionParts = ables[i].GetSelectionItemsToRender(this, graphMousePos); + foreach (IGraphPart selPart in selectionParts) selPart.Render(this, g, graphPens[i]); } } } diff --git a/Base/Forms/ViewCacheForm.cs b/Base/Forms/ViewCacheForm.cs index 7f767a9..4fa824b 100644 --- a/Base/Forms/ViewCacheForm.cs +++ b/Base/Forms/ViewCacheForm.cs @@ -33,6 +33,7 @@ public partial class ViewCacheForm : Form foreach (Graphable able in refForm.Graphables) { long thisBytes = able.GetCacheBytes(); + if (thisBytes == 0) continue; CachePie.Values.Add((able.Color, thisBytes)); totalBytes += thisBytes; diff --git a/Base/Graphable.cs b/Base/Graphable.cs index e59f358..75f2d8a 100644 --- a/Base/Graphable.cs +++ b/Base/Graphable.cs @@ -37,5 +37,5 @@ public abstract class Graphable public virtual void Preload(Float2 xRange, Float2 yRange, double step) { } public virtual bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) => false; - public virtual Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => default; + public virtual IEnumerable GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos) => []; } diff --git a/Base/Graphables/ColumnTable.cs b/Base/Graphables/ColumnTable.cs index 7b95593..da154fe 100644 --- a/Base/Graphables/ColumnTable.cs +++ b/Base/Graphables/ColumnTable.cs @@ -2,6 +2,7 @@ using Graphing.Parts; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; namespace Graphing.Graphables; @@ -52,6 +53,81 @@ public class ColumnTable : Graphable return items; } + public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) + { + // Get closest value to mouse pos. + double closestDist = double.PositiveInfinity, closestX = 0, closestY = 0; + foreach (KeyValuePair points in tableXY) + { + double dist = Math.Abs(points.Key - graphMousePos.x); + if (dist < closestDist) + { + closestDist = dist; + closestX = points.Key; + closestY = points.Value; + } + } + + Int2 screenMousePos = graph.GraphSpaceToScreenSpace(graphMousePos); + Int2 minBox = graph.GraphSpaceToScreenSpace(new(closestX - width / 2, 0)), + maxBox = graph.GraphSpaceToScreenSpace(new(closestX + width / 2, closestY)); + + int distX, distY; + if (screenMousePos.x < minBox.x) distX = minBox.x - screenMousePos.x; // On left side. + else if (screenMousePos.x > maxBox.x) distX = screenMousePos.x - maxBox.x; // On right side. + else distX = 0; // Inside. + + if (closestY > 0) + { + if (screenMousePos.y > minBox.y) distY = screenMousePos.y - minBox.y; // Underneath. + else if (screenMousePos.y < maxBox.y) distY = maxBox.y - screenMousePos.y; // Above. + else distY = 0; // Inside. + } + else + { + if (screenMousePos.y < minBox.y) distY = minBox.y - screenMousePos.y; // Underneath. + else if (screenMousePos.y > maxBox.y) distY = screenMousePos.y - maxBox.y; // Above. + else distY = 0; // Inside. + } + + int totalDist = (int)Math.Sqrt(distX * distX + distY * distY); + return totalDist < 50 * factor * graph.DpiFloat / 192; + } + public override IEnumerable GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos) + { + // Get closest value to mouse pos. + double closestDist = double.PositiveInfinity, closestX = 0, closestY = 0; + foreach (KeyValuePair points in tableXY) + { + double dist = Math.Abs(points.Key - graphMousePos.x); + if (dist < closestDist) + { + closestDist = dist; + closestX = points.Key; + closestY = points.Value; + } + } + + Float2 textPoint = new(closestX, closestY); + Int2 offset; + ContentAlignment alignment; + if (textPoint.y >= 0) + { + offset = new(0, -5); + alignment = ContentAlignment.BottomCenter; + } + else + { + offset = new(0, 5); + alignment = ContentAlignment.TopCenter; + } + + return + [ + new GraphUiText($"{closestY:0.00}", textPoint, alignment, offsetPix: offset) + ]; + } + // Nothing to preload, everything is already cached. public override void Preload(Float2 xRange, Float2 yRange, double step) { } } diff --git a/Base/Graphables/Equation.cs b/Base/Graphables/Equation.cs index a118299..e28deee 100644 --- a/Base/Graphables/Equation.cs +++ b/Base/Graphables/Equation.cs @@ -3,6 +3,7 @@ using Graphing.Forms; using Graphing.Parts; using System; using System.Collections.Generic; +using System.Drawing; namespace Graphing.Graphables; @@ -148,8 +149,15 @@ public class Equation : Graphable, IIntegrable, IDerivable, ITranslatableXY, ICo double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y); return totalDist <= allowedDist; } - public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => - new(graphMousePos.x, GetFromCache(graphMousePos.x, 1e-3)); + public override IEnumerable GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos) + { + Float2 point = new(graphMousePos.x, GetFromCache(graphMousePos.x, 1e-3)); + return + [ + new GraphUiText($"({point.x:0.00}, {point.y:0.00})", point, ContentAlignment.BottomLeft), + new GraphUiCircle(point), + ]; + } public override void Preload(Float2 xRange, Float2 yRange, double step) { diff --git a/Base/Graphables/EquationDifference.cs b/Base/Graphables/EquationDifference.cs index bdd341c..ebe02c7 100644 --- a/Base/Graphables/EquationDifference.cs +++ b/Base/Graphables/EquationDifference.cs @@ -1,7 +1,9 @@ using Graphing.Abstract; using Graphing.Forms; using Graphing.Parts; +using System; using System.Collections.Generic; +using System.Drawing; namespace Graphing.Graphables; @@ -42,16 +44,50 @@ public class EquationDifference : Graphable, ITranslatableX, IConvertEquation public override IEnumerable GetItemsToRender(in GraphForm graph) { Float2 pA = new(Position, points.x), - pB = new(Position, points.y), - pC = new(Position, (points.x + points.y) / 2); - return [new GraphUiText($"{points.x - points.y:0.00}", pC), - new GraphUiCircle(pA), new GraphUiCircle(pB), new GraphLine(pA, pB)]; + pB = new(Position, points.y); + return + [ + new GraphUiCircle(pA), + new GraphUiCircle(pB), + new GraphLine(pA, pB) + ]; } public double DistanceAtPoint(double x) => equA.GetValueAt(x) - equB.GetValueAt(x); public override Graphable ShallowCopy() => new EquationDifference(Position, equA, equB); + public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) + { + Float2 nearestPoint = new(Position, graphMousePos.y); + double upper = double.Max(points.x, points.y), + lower = double.Min(points.x, points.y); + if (nearestPoint.y > upper) nearestPoint.y = upper; + else if (nearestPoint.y < lower) nearestPoint.y = lower; + + Int2 nearestPixelPoint = graph.GraphSpaceToScreenSpace(nearestPoint); + Int2 screenMousePos = graph.GraphSpaceToScreenSpace(graphMousePos); + + Int2 diff = new(screenMousePos.x - nearestPixelPoint.x, + screenMousePos.y - nearestPixelPoint.y); + int dist = (int)Math.Sqrt(diff.x * diff.x + diff.y * diff.y); + return dist < 50 * factor * graph.DpiFloat / 192; + } + public override IEnumerable GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos) + { + Float2 nearestPoint = new(Position, graphMousePos.y); + double upper = double.Max(points.x, points.y), + lower = double.Min(points.x, points.y); + if (nearestPoint.y > upper) nearestPoint.y = upper; + else if (nearestPoint.y < lower) nearestPoint.y = lower; + + return + [ + new GraphUiText($"Δ = {points.x - points.y:0.000}", nearestPoint, ContentAlignment.MiddleLeft, offsetPix: new Int2(15, 0)), + new GraphUiCircle(nearestPoint) + ]; + } + public Equation ToEquation() => new(DistanceAtPoint) { Color = Color, diff --git a/Base/Graphables/IntegralEquation.cs b/Base/Graphables/IntegralEquation.cs index 8c49140..daf28d5 100644 --- a/Base/Graphables/IntegralEquation.cs +++ b/Base/Graphables/IntegralEquation.cs @@ -234,6 +234,6 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y); return totalDist <= allowedDist; } - public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) => - new(graphMousePos.x, IntegralAtPoint(graphMousePos.x)); + public override IEnumerable GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos) => + [new GraphUiCircle(new(graphMousePos.x, IntegralAtPoint(graphMousePos.x)))]; } diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs index d9a4549..701f506 100644 --- a/Base/Graphables/SlopeField.cs +++ b/Base/Graphables/SlopeField.cs @@ -2,6 +2,7 @@ using Graphing.Parts; using System; using System.Collections.Generic; +using System.Drawing; namespace Graphing.Graphables; @@ -101,7 +102,7 @@ public class SlopeField : Graphable double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y); return totalDist <= allowedDist; } - public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) + public override IEnumerable GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos) { Float2 nearestPos = new(Math.Round(graphMousePos.x * detail) / detail, Math.Round(graphMousePos.y * detail) / detail); @@ -114,7 +115,11 @@ public class SlopeField : Graphable lineY = slope * (lineX - nearestPos.x) + nearestPos.y; Float2 point = new(lineX, lineY); - return point; + return + [ + new GraphUiText($"M = {slope:0.000}", point, ContentAlignment.BottomLeft), + new GraphUiCircle(point) + ]; } public override void Preload(Float2 xRange, Float2 yRange, double step) diff --git a/Base/Graphables/TangentLine.cs b/Base/Graphables/TangentLine.cs index ab19db4..10faab3 100644 --- a/Base/Graphables/TangentLine.cs +++ b/Base/Graphables/TangentLine.cs @@ -3,6 +3,7 @@ using Graphing.Forms; using Graphing.Parts; using System; using System.Collections.Generic; +using System.Drawing; namespace Graphing.Graphables; @@ -58,7 +59,11 @@ public class TangentLine : Graphable, IConvertEquation, ITranslatableX public override IEnumerable GetItemsToRender(in GraphForm graph) { Float2 point = new(Position, currentSlope.y); - return [MakeSlopeLine(), new GraphUiCircle(point)]; + return + [ + MakeSlopeLine(), + new GraphUiCircle(point) + ]; } protected GraphLine MakeSlopeLine() { @@ -107,7 +112,7 @@ public class TangentLine : Graphable, IConvertEquation, ITranslatableX double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y); return totalDist <= allowedDist; } - public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) + public override IEnumerable GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos) { GraphLine line = MakeSlopeLine(); @@ -115,7 +120,15 @@ public class TangentLine : Graphable, IConvertEquation, ITranslatableX Math.Min(line.a.x, line.b.x), Math.Max(line.a.x, line.b.x)), lineY = currentSlope.x * (lineX - Position) + currentSlope.y; - return new Float2(lineX, lineY); + + double slope = currentSlope.x; + Float2 point = new(lineX, lineY); + + return + [ + new GraphUiText($"M = {slope:0.000}", point, ContentAlignment.BottomLeft), + new GraphUiCircle(new(lineX, lineY)) + ]; } public override void Preload(Float2 xRange, Float2 yRange, double step) diff --git a/Base/Parts/GraphRectangle.cs b/Base/Parts/GraphRectangle.cs index 161d7e2..1d4cac6 100644 --- a/Base/Parts/GraphRectangle.cs +++ b/Base/Parts/GraphRectangle.cs @@ -45,7 +45,9 @@ public record struct GraphRectangle : IGraphPart start.y - end.y); if (size.x == 0 || size.y == 0) return; + Color initialColor = pen.Color; pen.Color = Color.FromArgb((int)(opacity * 255), pen.Color); g.FillRectangle(pen.Brush, new Rectangle(start.x, end.y, size.x, size.y)); + pen.Color = initialColor; } } diff --git a/Base/Parts/GraphUiText.cs b/Base/Parts/GraphUiText.cs index 0d27573..420e006 100644 --- a/Base/Parts/GraphUiText.cs +++ b/Base/Parts/GraphUiText.cs @@ -9,16 +9,22 @@ public record struct GraphUiText : IGraphPart public Float2 position; public bool background; + public ContentAlignment alignment; + public Int2 offsetPix; + private readonly Font font; private readonly Brush? backgroundBrush; - public GraphUiText(string text, Float2 position, bool background = true) + public GraphUiText(string text, Float2 position, ContentAlignment alignment, + bool background = true, Int2? offsetPix = null) { font = new Font("Segoe UI", 8, FontStyle.Bold); this.text = text; this.position = position; this.background = background; + this.alignment = alignment; + this.offsetPix = offsetPix ?? new(); if (background) backgroundBrush = new SolidBrush(GraphForm.BackgroundColor); } @@ -26,10 +32,53 @@ public record struct GraphUiText : IGraphPart public readonly void Render(in GraphForm form, in Graphics g, in Pen p) { Int2 posScreen = form.GraphSpaceToScreenSpace(position); - posScreen.y -= (int)(form.DpiFloat * font.Size * 2 / 192); + SizeF size = g.MeasureString(text, font); + + // Adjust X position based on alignment. + switch (alignment) + { + case ContentAlignment.TopLeft or + ContentAlignment.MiddleLeft or + ContentAlignment.BottomLeft: break; // Nothing to offset. + + case ContentAlignment.TopCenter or + ContentAlignment.MiddleCenter or + ContentAlignment.BottomCenter: + posScreen.x -= (int)(size.Width / 2); + break; + + case ContentAlignment.TopRight or + ContentAlignment.MiddleRight or + ContentAlignment.BottomRight: + posScreen.x -= (int)size.Width; + break; + } + + // Adjust Y position based on alignment. + switch (alignment) + { + case ContentAlignment.TopLeft or + ContentAlignment.TopCenter or + ContentAlignment.TopRight: break; // Nothing to offset. + + case ContentAlignment.MiddleLeft or + ContentAlignment.MiddleCenter or + ContentAlignment.MiddleRight: + posScreen.y -= (int)(size.Height / 2); + break; + + case ContentAlignment.BottomLeft or + ContentAlignment.BottomCenter or + ContentAlignment.BottomRight: + posScreen.y -= (int)size.Height; + break; + } + + posScreen.x += (int)(offsetPix.x * form.DpiFloat / 192); + posScreen.y += (int)(offsetPix.y * form.DpiFloat / 192); + if (background) { - SizeF size = g.MeasureString(text, font); g.FillRectangle(backgroundBrush!, new Rectangle(posScreen.x, posScreen.y, (int)size.Width, (int)size.Height)); } diff --git a/Testing/Program.cs b/Testing/Program.cs index ef0bc3f..35082d7 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -20,7 +20,8 @@ internal static class Program equB = new(Math.Cos); EquationDifference diff = new(2, equA, equB); ParametricEquation equC = new(0, 20, t => 0.0375 * t * Math.Cos(t), t => 0.0625 * t * Math.Sin(t) + 3); - graph.Graph(equA, equB, diff, equC, equA.ToColumnTable(-1, 1, 2)); + TangentLine tanA = new(2, 2, equA); + graph.Graph(equA, equB, diff, equC, equB.ToColumnTable(-3, 3, 2), tanA); Application.Run(graph); } -- 2.49.0.windows.1 From 3ab55b509abe24624f21cbaea381e867a0be076d Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Mon, 8 Apr 2024 09:56:28 -0400 Subject: [PATCH 11/21] Basic system written. Now I need to add tools for it. Had to delete the SetZoomForm. --- Base/Base.csproj.user | 3 - Base/Forms/GraphForm.cs | 72 +++++++---------- Base/Forms/SetZoomForm.Designer.cs | 120 ---------------------------- Base/Forms/SetZoomForm.cs | 122 ----------------------------- Base/Forms/SetZoomForm.resx | 120 ---------------------------- 5 files changed, 29 insertions(+), 408 deletions(-) delete mode 100644 Base/Forms/SetZoomForm.Designer.cs delete mode 100644 Base/Forms/SetZoomForm.cs delete mode 100644 Base/Forms/SetZoomForm.resx diff --git a/Base/Base.csproj.user b/Base/Base.csproj.user index b8c9d44..a1412bd 100644 --- a/Base/Base.csproj.user +++ b/Base/Base.csproj.user @@ -13,9 +13,6 @@ Form - - Form - Form diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 8f75202..b8e0d2e 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -27,26 +27,16 @@ public partial class GraphForm : Form public float DpiFloat { get; private set; } - public double ZoomLevel + public Float2 ZoomLevel { get => _zoomLevel; set { - double oldZoom = ZoomLevel; - - _zoomLevel = Math.Clamp(value, 1e-5, 1e3); - - int totalSegments = 0; - foreach (Graphable able in ables) totalSegments += able.GetItemsToRender(this).Count(); - - if (totalSegments > 10_000) - { - _zoomLevel = oldZoom; - return; // Too many segments, stop. - } + _zoomLevel = new(Math.Clamp(value.x, 1e-5, 1e3), + Math.Clamp(value.y, 1e-5, 1e3)); } } - private double _zoomLevel; + private Float2 _zoomLevel; private readonly Point initialWindowPos; private readonly Size initialWindowSize; @@ -74,7 +64,7 @@ public partial class GraphForm : Form DpiFloat = (float)((Dpi.x + Dpi.y) / 2); ables = []; - ZoomLevel = 1; + ZoomLevel = new(1, 1); initialWindowPos = Location; initialWindowSize = Size; @@ -88,8 +78,8 @@ public partial class GraphForm : Form graphPoint.x -= ScreenCenter.x; graphPoint.y -= ScreenCenter.y; - graphPoint.x *= Dpi.x / ZoomLevel; - graphPoint.y *= Dpi.y / ZoomLevel; + graphPoint.x *= Dpi.x / ZoomLevel.x; + graphPoint.y *= Dpi.y / ZoomLevel.y; graphPoint.x += ClientRectangle.Width / 2.0; graphPoint.y += ClientRectangle.Height / 2.0; @@ -103,8 +93,8 @@ public partial class GraphForm : Form result.x -= ClientRectangle.Width / 2.0; result.y -= ClientRectangle.Height / 2.0; - result.x /= Dpi.x / ZoomLevel; - result.y /= Dpi.y / ZoomLevel; + result.x /= Dpi.x / ZoomLevel.x; + result.y /= Dpi.y / ZoomLevel.y; result.x += ScreenCenter.x; result.y += ScreenCenter.y; @@ -116,19 +106,20 @@ public partial class GraphForm : Form protected virtual void PaintGrid(Graphics g) { - double axisScale = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel))); + double axisScaleX = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel.x))), + axisScaleY = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel.y))); // Draw horizontal/vertical quarter-axis. Brush quarterBrush = new SolidBrush(QuarterAxisColor); Pen quarterPen = new(quarterBrush, DpiFloat * 2 / 192); - for (double x = Math.Ceiling(MinVisibleGraph.x * 4 / axisScale) * axisScale / 4; x <= Math.Floor(MaxVisibleGraph.x * 4 / axisScale) * axisScale / 4; x += axisScale / 4) + for (double x = Math.Ceiling(MinVisibleGraph.x * 4 / axisScaleX) * axisScaleX / 4; x <= Math.Floor(MaxVisibleGraph.x * 4 / axisScaleX) * axisScaleX / 4; x += axisScaleX / 4) { Int2 startPos = GraphSpaceToScreenSpace(new Float2(x, MinVisibleGraph.y)), endPos = GraphSpaceToScreenSpace(new Float2(x, MaxVisibleGraph.y)); g.DrawLine(quarterPen, startPos, endPos); } - for (double y = Math.Ceiling(MinVisibleGraph.y * 4 / axisScale) * axisScale / 4; y <= Math.Floor(MaxVisibleGraph.y * 4 / axisScale) * axisScale / 4; y += axisScale / 4) + for (double y = Math.Ceiling(MinVisibleGraph.y * 4 / axisScaleY) * axisScaleY / 4; y <= Math.Floor(MaxVisibleGraph.y * 4 / axisScaleY) * axisScaleY / 4; y += axisScaleY / 4) { Int2 startPos = GraphSpaceToScreenSpace(new Float2(MinVisibleGraph.x, y)), endPos = GraphSpaceToScreenSpace(new Float2(MaxVisibleGraph.x, y)); @@ -139,13 +130,13 @@ public partial class GraphForm : Form Brush semiBrush = new SolidBrush(SemiAxisColor); Pen semiPen = new(semiBrush, DpiFloat * 2 / 192); - for (double x = Math.Ceiling(MinVisibleGraph.x / axisScale) * axisScale; x <= Math.Floor(MaxVisibleGraph.x / axisScale) * axisScale; x += axisScale) + for (double x = Math.Ceiling(MinVisibleGraph.x / axisScaleX) * axisScaleX; x <= Math.Floor(MaxVisibleGraph.x / axisScaleX) * axisScaleX; x += axisScaleX) { Int2 startPos = GraphSpaceToScreenSpace(new Float2(x, MinVisibleGraph.y)), endPos = GraphSpaceToScreenSpace(new Float2(x, MaxVisibleGraph.y)); g.DrawLine(semiPen, startPos, endPos); } - for (double y = Math.Ceiling(MinVisibleGraph.y / axisScale) * axisScale; y <= Math.Floor(MaxVisibleGraph.y / axisScale) * axisScale; y += axisScale) + for (double y = Math.Ceiling(MinVisibleGraph.y / axisScaleY) * axisScaleY; y <= Math.Floor(MaxVisibleGraph.y / axisScaleY) * axisScaleY; y += axisScaleY) { Int2 startPos = GraphSpaceToScreenSpace(new Float2(MinVisibleGraph.x, y)), endPos = GraphSpaceToScreenSpace(new Float2(MaxVisibleGraph.x, y)); @@ -166,14 +157,15 @@ public partial class GraphForm : Form } protected virtual void PaintUnits(Graphics g) { - double axisScale = Math.Pow(2, Math.Round(Math.Log(ZoomLevel, 2))); + double axisScaleX = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel.x))), + axisScaleY = Math.Pow(2, Math.Round(Math.Log2(ZoomLevel.y))); Brush textBrush = new SolidBrush(UnitsTextColor); Font textFont = new(Font.Name, 9, FontStyle.Regular); // X-axis int minX = (int)(DpiFloat * 50 / 192), maxX = ClientRectangle.Height - (int)(DpiFloat * 40 / 192); - for (double x = Math.Ceiling(MinVisibleGraph.x / axisScale) * axisScale; x <= MaxVisibleGraph.x; x += axisScale) + for (double x = Math.Ceiling(MinVisibleGraph.x / axisScaleX) * axisScaleX; x <= MaxVisibleGraph.x; x += axisScaleX) { if (x == 0) x = 0; // Fixes -0 @@ -187,7 +179,7 @@ public partial class GraphForm : Form // Y-axis int minY = (int)(DpiFloat * 10 / 192); - for (double y = Math.Ceiling(MinVisibleGraph.y / axisScale) * axisScale; y <= MaxVisibleGraph.y; y += axisScale) + for (double y = Math.Ceiling(MinVisibleGraph.y / axisScaleY) * axisScaleY; y <= MaxVisibleGraph.y; y += axisScaleY) { if (y == 0) continue; @@ -304,7 +296,7 @@ public partial class GraphForm : Form { Int2 pixelDiff = new(initialMouseLocation.x - Cursor.Position.X, initialMouseLocation.y - Cursor.Position.Y); - Float2 graphDiff = new(pixelDiff.x * ZoomLevel / Dpi.x, pixelDiff.y * ZoomLevel / Dpi.y); + Float2 graphDiff = new(pixelDiff.x * ZoomLevel.x / Dpi.x, pixelDiff.y * ZoomLevel.y / Dpi.y); ScreenCenter = new(initialScreenCenter.x + graphDiff.x, initialScreenCenter.y + graphDiff.y); } @@ -318,7 +310,7 @@ public partial class GraphForm : Form { Int2 pixelDiff = new(initialMouseLocation.x - Cursor.Position.X, initialMouseLocation.y - Cursor.Position.Y); - Float2 graphDiff = new(pixelDiff.x * ZoomLevel / Dpi.x, pixelDiff.y * ZoomLevel / Dpi.y); + Float2 graphDiff = new(pixelDiff.x * ZoomLevel.x / Dpi.x, pixelDiff.y * ZoomLevel.y / Dpi.y); ScreenCenter = new(initialScreenCenter.x + graphDiff.x, initialScreenCenter.y + graphDiff.y); Invalidate(false); @@ -327,14 +319,18 @@ public partial class GraphForm : Form } protected override void OnMouseWheel(MouseEventArgs e) { - ZoomLevel *= 1 - e.Delta * 0.00075; // Zoom factor. + Float2 newZoom = ZoomLevel; + newZoom.x *= 1 - e.Delta * 0.00075; // Zoom factor. + newZoom.y *= 1 - e.Delta * 0.00075; + ZoomLevel = newZoom; + Invalidate(false); } private void ResetViewportButton_Click(object? sender, EventArgs e) { ScreenCenter = new Float2(0, 0); - ZoomLevel = 1; + ZoomLevel = new(1, 1); Invalidate(false); } private void GraphColorPickerButton_Click(Graphable able) @@ -449,17 +445,7 @@ public partial class GraphForm : Form private void ButtonViewportSetZoom_Click(object? sender, EventArgs e) { - SetZoomForm zoomer = new(this) - { - StartPosition = FormStartPosition.Manual, - }; - zoomer.Location = new Point(Location.X + ClientRectangle.Width + 10, - Location.Y + (ClientRectangle.Height - zoomer.ClientRectangle.Height) / 2); - if (zoomer.Location.X + zoomer.Width > Screen.FromControl(this).WorkingArea.Width) - { - zoomer.StartPosition = FormStartPosition.WindowsDefaultLocation; - } - zoomer.ShowDialog(); + MessageBox.Show("TODO", "Set Viewport Zoom", MessageBoxButtons.OK, MessageBoxIcon.Error); } private void ButtonViewportSetCenter_Click(object? sender, EventArgs e) { @@ -468,7 +454,7 @@ public partial class GraphForm : Form private void ButtonViewportReset_Click(object? sender, EventArgs e) { ScreenCenter = new Float2(0, 0); - ZoomLevel = 1; + ZoomLevel = new(1, 1); Invalidate(false); } private void ButtonViewportResetWindow_Click(object? sender, EventArgs e) diff --git a/Base/Forms/SetZoomForm.Designer.cs b/Base/Forms/SetZoomForm.Designer.cs deleted file mode 100644 index 52990db..0000000 --- a/Base/Forms/SetZoomForm.Designer.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Drawing; -using System.Windows.Forms; - -namespace Graphing.Forms -{ - partial class SetZoomForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - MessageLabel = new Label(); - ZoomTrackBar = new TrackBar(); - ValueLabel = new Label(); - ZoomMinValue = new TextBox(); - ZoomMaxValue = new TextBox(); - ((System.ComponentModel.ISupportInitialize)ZoomTrackBar).BeginInit(); - SuspendLayout(); - // - // MessageLabel - // - MessageLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - MessageLabel.Location = new Point(52, 20); - MessageLabel.Name = "MessageLabel"; - MessageLabel.Size = new Size(413, 35); - MessageLabel.TabIndex = 0; - MessageLabel.Text = "Set the zoom level for the graph."; - MessageLabel.TextAlign = ContentAlignment.MiddleCenter; - // - // ZoomTrackBar - // - ZoomTrackBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - ZoomTrackBar.LargeChange = 1000; - ZoomTrackBar.Location = new Point(12, 127); - ZoomTrackBar.Maximum = 10000; - ZoomTrackBar.Name = "ZoomTrackBar"; - ZoomTrackBar.Size = new Size(489, 90); - ZoomTrackBar.TabIndex = 1; - ZoomTrackBar.TickStyle = TickStyle.None; - ZoomTrackBar.Scroll += ZoomTrackBar_Scroll; - // - // ValueLabel - // - ValueLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - ValueLabel.Location = new Point(52, 91); - ValueLabel.Name = "ValueLabel"; - ValueLabel.Size = new Size(413, 33); - ValueLabel.TabIndex = 2; - ValueLabel.Text = "1.00x"; - ValueLabel.TextAlign = ContentAlignment.TopCenter; - // - // ZoomMinValue - // - ZoomMinValue.Location = new Point(12, 178); - ZoomMinValue.Name = "ZoomMinValue"; - ZoomMinValue.Size = new Size(83, 39); - ZoomMinValue.TabIndex = 3; - ZoomMinValue.Text = "0.50"; - ZoomMinValue.TextChanged += ZoomMinValue_TextChanged; - // - // ZoomMaxValue - // - ZoomMaxValue.Anchor = AnchorStyles.Top | AnchorStyles.Right; - ZoomMaxValue.Location = new Point(418, 178); - ZoomMaxValue.Name = "ZoomMaxValue"; - ZoomMaxValue.Size = new Size(83, 39); - ZoomMaxValue.TabIndex = 4; - ZoomMaxValue.Text = "2.00"; - ZoomMaxValue.TextAlign = HorizontalAlignment.Right; - ZoomMaxValue.TextChanged += ZoomMaxValue_TextChanged; - // - // SetZoomForm - // - AutoScaleDimensions = new SizeF(13F, 32F); - AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(513, 230); - Controls.Add(ZoomMaxValue); - Controls.Add(ZoomMinValue); - Controls.Add(ValueLabel); - Controls.Add(ZoomTrackBar); - Controls.Add(MessageLabel); - FormBorderStyle = FormBorderStyle.FixedToolWindow; - Name = "SetZoomForm"; - Text = "Zoom Level"; - ((System.ComponentModel.ISupportInitialize)ZoomTrackBar).EndInit(); - ResumeLayout(false); - PerformLayout(); - } - - #endregion - - private Label MessageLabel; - private TrackBar ZoomTrackBar; - private Label ValueLabel; - private TextBox ZoomMinValue; - private TextBox ZoomMaxValue; - } -} \ No newline at end of file diff --git a/Base/Forms/SetZoomForm.cs b/Base/Forms/SetZoomForm.cs deleted file mode 100644 index fc21296..0000000 --- a/Base/Forms/SetZoomForm.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Windows.Forms; - -namespace Graphing.Forms; - -public partial class SetZoomForm : Form -{ - private double minZoomRange; - private double maxZoomRange; - - private double zoomLevel; - - private readonly GraphForm form; - - public SetZoomForm(GraphForm form) - { - InitializeComponent(); - - minZoomRange = 1 / (form.ZoomLevel * 2); - maxZoomRange = 2 / form.ZoomLevel; - zoomLevel = 1 / form.ZoomLevel; - - ZoomTrackBar.Value = (int)(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum); - - this.form = form; - } - - protected override void OnPaint(PaintEventArgs e) - { - ZoomMaxValue.Text = maxZoomRange.ToString("0.00"); - ZoomMinValue.Text = minZoomRange.ToString("0.00"); - - ValueLabel.Text = $"{zoomLevel:0.00}x"; - - base.OnPaint(e); - - form.ZoomLevel = 1 / zoomLevel; - form.Invalidate(false); - } - - private double FactorToZoom(double factor) - { - return minZoomRange + (factor * factor) * (maxZoomRange - minZoomRange); - } - private double ZoomToFactor(double zoom) - { - double sqrValue = (zoom - minZoomRange) / (maxZoomRange - minZoomRange); - return Math.Sign(sqrValue) * Math.Sqrt(Math.Abs(sqrValue)); - } - - private void ZoomTrackBar_Scroll(object? sender, EventArgs e) - { - double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum); - zoomLevel = FactorToZoom(factor); - - Invalidate(true); - } - - private void ZoomMinValue_TextChanged(object? sender, EventArgs e) - { - double original = minZoomRange; - try - { - double value; - if (string.IsNullOrWhiteSpace(ZoomMinValue.Text) || - ZoomMinValue.Text.EndsWith('.')) - { - return; - } - else - { - value = double.Parse(ZoomMinValue.Text); - if (value < 1e-2 || value > 1e3 || value > maxZoomRange) throw new(); - } - - minZoomRange = value; - ZoomTrackBar.Value = (int)Math.Clamp(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum, ZoomTrackBar.Minimum, ZoomTrackBar.Maximum); - double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum); - double newZoom = FactorToZoom(factor); - - zoomLevel = newZoom; - if (newZoom != factor) Invalidate(true); - } - catch - { - minZoomRange = original; - ZoomMinValue.Text = minZoomRange.ToString("0.00"); - } - } - - private void ZoomMaxValue_TextChanged(object sender, EventArgs e) - { - double original = maxZoomRange; - try - { - double value; - if (string.IsNullOrWhiteSpace(ZoomMaxValue.Text) || - ZoomMaxValue.Text.EndsWith('.')) - { - return; - } - else - { - value = double.Parse(ZoomMaxValue.Text); - if (value < 1e-2 || value > 1e3 || value < minZoomRange) throw new(); - } - - maxZoomRange = value; - ZoomTrackBar.Value = (int)Math.Clamp(ZoomToFactor(zoomLevel) * (ZoomTrackBar.Maximum - ZoomTrackBar.Minimum) + ZoomTrackBar.Minimum, ZoomTrackBar.Minimum, ZoomTrackBar.Maximum); - double factor = (ZoomTrackBar.Value - ZoomTrackBar.Minimum) / (double)(ZoomTrackBar.Maximum - ZoomTrackBar.Minimum); - double newZoom = FactorToZoom(factor); - - zoomLevel = newZoom; - if (newZoom != factor) Invalidate(true); - } - catch - { - maxZoomRange = original; - ZoomMaxValue.Text = maxZoomRange.ToString("0.00"); - } - } -} diff --git a/Base/Forms/SetZoomForm.resx b/Base/Forms/SetZoomForm.resx deleted file mode 100644 index af32865..0000000 --- a/Base/Forms/SetZoomForm.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file -- 2.49.0.windows.1 From 933fcdf0828f77b42c8dd3720d8fdb1ebd674193 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Tue, 16 Apr 2024 09:43:16 -0400 Subject: [PATCH 12/21] This took way too long. Zoom-box system works. --- Base/Base.csproj.user | 3 + Base/Forms/GraphForm.cs | 133 +++++++++++++++++++++++++---- Base/Forms/SetZoomForm.Designer.cs | 60 +++++++++++++ Base/Forms/SetZoomForm.cs | 38 +++++++++ Base/Forms/SetZoomForm.resx | 120 ++++++++++++++++++++++++++ 5 files changed, 338 insertions(+), 16 deletions(-) create mode 100644 Base/Forms/SetZoomForm.Designer.cs create mode 100644 Base/Forms/SetZoomForm.cs create mode 100644 Base/Forms/SetZoomForm.resx diff --git a/Base/Base.csproj.user b/Base/Base.csproj.user index a1412bd..b8c9d44 100644 --- a/Base/Base.csproj.user +++ b/Base/Base.csproj.user @@ -13,6 +13,9 @@ Form + + Form + Form diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index b8e0d2e..e4d22f5 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -21,6 +21,7 @@ public partial class GraphForm : Form public static readonly Color SemiAxisColor = Color.FromArgb(unchecked((int)0xFF_999999)); public static readonly Color QuarterAxisColor = Color.FromArgb(unchecked((int)0xFF_E0E0E0)); public static readonly Color UnitsTextColor = Color.Black; + public static readonly Color ZoomBoxColor = Color.Black; public Float2 ScreenCenter { get; private set; } public Float2 Dpi { get; private set; } @@ -223,9 +224,8 @@ public partial class GraphForm : Form // Equation selection detection. // This system lets you select multiple graphs, and that's cool by me. - if (ableDrag) + if (selectState == SelectionState.GraphSelect) { - Font textFont = new(Font.Name, 8, FontStyle.Bold); for (int i = 0; i < ables.Count; i++) { if (ables[i].ShouldSelectGraphable(this, graphMousePos, 2.5)) @@ -235,6 +235,20 @@ public partial class GraphForm : Form } } } + else if (selectState == SelectionState.ZoomBox) + { + // Draw the current box selection. + Int2 boxPosA = GraphSpaceToScreenSpace(boxSelectA), + boxPosB = GraphSpaceToScreenSpace(boxSelectB); + + if (boxPosA.x > boxPosB.x) (boxPosA.x, boxPosB.x) = (boxPosB.x, boxPosA.x); + if (boxPosA.y > boxPosB.y) (boxPosA.y, boxPosB.y) = (boxPosB.y, boxPosA.y); + + Pen boxPen = new(ZoomBoxColor, 2 * DpiFloat / 192); + g.DrawRectangle(boxPen, new(boxPosA.x, boxPosA.y, + boxPosB.x - boxPosA.x, + boxPosB.y - boxPosA.y)); + } base.OnPaint(e); } @@ -264,35 +278,52 @@ public partial class GraphForm : Form pixelPos.y >= 0 && pixelPos.y < ClientRectangle.Height; } - private bool mouseDrag = false; + private SelectionState selectState = SelectionState.None; + internal bool canBoxSelect; + private SetZoomForm? setZoomForm; + private Int2 initialMouseLocation; private Float2 initialScreenCenter; - private bool ableDrag = false; + private Float2 boxSelectA, boxSelectB; + protected override void OnMouseDown(MouseEventArgs e) { - if (!mouseDrag) + if (selectState == SelectionState.None && canBoxSelect) + { + Point clientMousePos = PointToClient(Cursor.Position); + Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X, + clientMousePos.Y)); + + boxSelectA = graphMousePos; + boxSelectB = graphMousePos; + + selectState = SelectionState.ZoomBox; + } + + if (selectState == SelectionState.None) { Point clientMousePos = PointToClient(Cursor.Position); Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X, clientMousePos.Y)); foreach (Graphable able in Graphables) { - if (able.ShouldSelectGraphable(this, graphMousePos, 1)) ableDrag = true; + if (able.ShouldSelectGraphable(this, graphMousePos, 1)) + selectState = SelectionState.GraphSelect; } - if (ableDrag) Invalidate(false); + if (selectState == SelectionState.GraphSelect) Invalidate(false); } - if (!ableDrag) + if (selectState == SelectionState.None) { - mouseDrag = true; + selectState = SelectionState.ViewportDrag; initialMouseLocation = new Int2(Cursor.Position.X, Cursor.Position.Y); initialScreenCenter = ScreenCenter; } } protected override void OnMouseUp(MouseEventArgs e) { - if (mouseDrag) + if (selectState == SelectionState.ViewportDrag) { Int2 pixelDiff = new(initialMouseLocation.x - Cursor.Position.X, initialMouseLocation.y - Cursor.Position.Y); @@ -300,22 +331,52 @@ public partial class GraphForm : Form ScreenCenter = new(initialScreenCenter.x + graphDiff.x, initialScreenCenter.y + graphDiff.y); } - mouseDrag = false; - ableDrag = false; + else if (selectState == SelectionState.ZoomBox) + { + Point clientMousePos = PointToClient(Cursor.Position); + Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X, + clientMousePos.Y)); + boxSelectB = graphMousePos; + + // Set center. + ScreenCenter = new((boxSelectA.x + boxSelectB.x) * 0.5, + -(boxSelectA.y + boxSelectB.y) * 0.5); + + // Set zoom. Kind of weird but it works. + Float2 minGraph = MinVisibleGraph, maxGraph = MaxVisibleGraph; + Float2 oldDist = new(maxGraph.x - minGraph.x, + maxGraph.y - minGraph.y); + Float2 newDist = new(Math.Abs(boxSelectB.x - boxSelectA.x), + Math.Abs(boxSelectB.y - boxSelectA.y)); + ZoomLevel = new(ZoomLevel.x * newDist.x / oldDist.x, + ZoomLevel.y * newDist.y / oldDist.y); + + setZoomForm!.CompleteBoxSelection(); + + boxSelectA = new(0, 0); + boxSelectB = new(0, 0); + } + selectState = SelectionState.None; Invalidate(false); } protected override void OnMouseMove(MouseEventArgs e) { - if (mouseDrag) + if (selectState == SelectionState.ViewportDrag) { Int2 pixelDiff = new(initialMouseLocation.x - Cursor.Position.X, initialMouseLocation.y - Cursor.Position.Y); Float2 graphDiff = new(pixelDiff.x * ZoomLevel.x / Dpi.x, pixelDiff.y * ZoomLevel.y / Dpi.y); ScreenCenter = new(initialScreenCenter.x + graphDiff.x, initialScreenCenter.y + graphDiff.y); - Invalidate(false); } - else if (ableDrag) Invalidate(false); + else if (selectState == SelectionState.ZoomBox) + { + Point clientMousePos = PointToClient(Cursor.Position); + Float2 graphMousePos = ScreenSpaceToGraphSpace(new(clientMousePos.X, + clientMousePos.Y)); + boxSelectB = graphMousePos; + } + Invalidate(false); } protected override void OnMouseWheel(MouseEventArgs e) { @@ -445,7 +506,31 @@ public partial class GraphForm : Form private void ButtonViewportSetZoom_Click(object? sender, EventArgs e) { - MessageBox.Show("TODO", "Set Viewport Zoom", MessageBoxButtons.OK, MessageBoxIcon.Error); + if (setZoomForm is not null) + { + setZoomForm.Focus(); + return; + } + + SetZoomForm zoomForm = new(this) + { + StartPosition = FormStartPosition.Manual, + }; + zoomForm.Location = new Point(Location.X + ClientRectangle.Width + 10, + Location.Y + (ClientRectangle.Height - zoomForm.ClientRectangle.Height) / 2); + + if (zoomForm.Location.X + zoomForm.Width > Screen.FromControl(this).WorkingArea.Width) + { + zoomForm.StartPosition = FormStartPosition.WindowsDefaultLocation; + } + + setZoomForm = zoomForm; + zoomForm.Show(); + zoomForm.FormClosing += (o, e) => + { + zoomForm.CompleteBoxSelection(); + setZoomForm = null; + }; } private void ButtonViewportSetCenter_Click(object? sender, EventArgs e) { @@ -464,12 +549,20 @@ public partial class GraphForm : Form WindowState = FormWindowState.Normal; } + private ViewCacheForm? cacheForm; private void MenuMiscCaches_Click(object? sender, EventArgs e) { + if (this.cacheForm is not null) + { + this.cacheForm.Focus(); + return; + } + ViewCacheForm cacheForm = new(this) { StartPosition = FormStartPosition.Manual }; + this.cacheForm = cacheForm; cacheForm.Location = new Point(Location.X + ClientRectangle.Width + 10, Location.Y + (ClientRectangle.Height - cacheForm.ClientRectangle.Height) / 2); @@ -594,4 +687,12 @@ public partial class GraphForm : Form Console.WriteLine($"Failed to check for updates:\n{ex}"); } } + + private enum SelectionState + { + None = 0, + ViewportDrag, + GraphSelect, + ZoomBox, + } } diff --git a/Base/Forms/SetZoomForm.Designer.cs b/Base/Forms/SetZoomForm.Designer.cs new file mode 100644 index 0000000..d3324cd --- /dev/null +++ b/Base/Forms/SetZoomForm.Designer.cs @@ -0,0 +1,60 @@ +namespace Graphing.Forms +{ + partial class SetZoomForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + EnableBoxSelect = new System.Windows.Forms.Button(); + SuspendLayout(); + // + // EnableBoxSelect + // + EnableBoxSelect.Location = new System.Drawing.Point(12, 12); + EnableBoxSelect.Name = "EnableBoxSelect"; + EnableBoxSelect.Size = new System.Drawing.Size(150, 46); + EnableBoxSelect.TabIndex = 0; + EnableBoxSelect.Text = "Box Select"; + EnableBoxSelect.UseVisualStyleBackColor = true; + EnableBoxSelect.Click += EnableBoxSelect_Click; + // + // SetZoomForm + // + AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(800, 450); + Controls.Add(EnableBoxSelect); + FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + Name = "SetZoomForm"; + Text = "Set Viewport Zoom"; + ResumeLayout(false); + } + + #endregion + + private System.Windows.Forms.Button EnableBoxSelect; + } +} \ No newline at end of file diff --git a/Base/Forms/SetZoomForm.cs b/Base/Forms/SetZoomForm.cs new file mode 100644 index 0000000..3a8c499 --- /dev/null +++ b/Base/Forms/SetZoomForm.cs @@ -0,0 +1,38 @@ +using System; +using System.Windows.Forms; + +namespace Graphing.Forms; + +public partial class SetZoomForm : Form +{ + private readonly GraphForm refForm; + + private bool boxSelectEnabled; + + public SetZoomForm(GraphForm refForm) + { + InitializeComponent(); + this.refForm = refForm; + } + + private void EnableBoxSelect_Click(object? sender, EventArgs e) + { + boxSelectEnabled = !boxSelectEnabled; + refForm.canBoxSelect = boxSelectEnabled; + + if (boxSelectEnabled) + { + EnableBoxSelect.Text = $"Cancel ..."; + refForm.Focus(); + } + else + { + EnableBoxSelect.Text = "Box Select"; + } + } + + internal void CompleteBoxSelection() + { + if (boxSelectEnabled) EnableBoxSelect_Click(null, new()); + } +} diff --git a/Base/Forms/SetZoomForm.resx b/Base/Forms/SetZoomForm.resx new file mode 100644 index 0000000..af32865 --- /dev/null +++ b/Base/Forms/SetZoomForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file -- 2.49.0.windows.1 From a6f7279b979d735cec93c386025ea71f8bf4eec8 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Wed, 17 Apr 2024 14:09:34 -0400 Subject: [PATCH 13/21] Zoom form finally pretty much ready. --- Base/Forms/GraphForm.cs | 18 +++++++++++--- Base/Forms/SetZoomForm.Designer.cs | 39 ++++++++++++++++++++++++++++++ Base/Forms/SetZoomForm.cs | 39 ++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index e4d22f5..211ea2c 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -35,6 +35,8 @@ public partial class GraphForm : Form { _zoomLevel = new(Math.Clamp(value.x, 1e-5, 1e3), Math.Clamp(value.y, 1e-5, 1e3)); + OnZoomLevelChanged(this, new()); + Invalidate(false); } } private Float2 _zoomLevel; @@ -49,6 +51,8 @@ public partial class GraphForm : Form private readonly List ables; + public event EventHandler OnZoomLevelChanged = delegate { }; + public GraphForm(string title) { SetStyle(ControlStyles.OptimizedDoubleBuffer, true); @@ -390,9 +394,7 @@ public partial class GraphForm : Form private void ResetViewportButton_Click(object? sender, EventArgs e) { - ScreenCenter = new Float2(0, 0); - ZoomLevel = new(1, 1); - Invalidate(false); + ResetAllViewport(); } private void GraphColorPickerButton_Click(Graphable able) { @@ -549,6 +551,16 @@ public partial class GraphForm : Form WindowState = FormWindowState.Normal; } + public void ResetAllViewport() + { + ScreenCenter = new Float2(0, 0); + ZoomLevel = new(1, 1); + Location = initialWindowPos; + Size = initialWindowSize; + WindowState = FormWindowState.Normal; + Invalidate(false); + } + private ViewCacheForm? cacheForm; private void MenuMiscCaches_Click(object? sender, EventArgs e) { diff --git a/Base/Forms/SetZoomForm.Designer.cs b/Base/Forms/SetZoomForm.Designer.cs index d3324cd..c8d464b 100644 --- a/Base/Forms/SetZoomForm.Designer.cs +++ b/Base/Forms/SetZoomForm.Designer.cs @@ -29,6 +29,9 @@ private void InitializeComponent() { EnableBoxSelect = new System.Windows.Forms.Button(); + MatchAspectButton = new System.Windows.Forms.Button(); + ResetButton = new System.Windows.Forms.Button(); + NormalizeButton = new System.Windows.Forms.Button(); SuspendLayout(); // // EnableBoxSelect @@ -41,11 +44,44 @@ EnableBoxSelect.UseVisualStyleBackColor = true; EnableBoxSelect.Click += EnableBoxSelect_Click; // + // MatchAspectButton + // + MatchAspectButton.Location = new System.Drawing.Point(168, 12); + MatchAspectButton.Name = "MatchAspectButton"; + MatchAspectButton.Size = new System.Drawing.Size(187, 46); + MatchAspectButton.TabIndex = 1; + MatchAspectButton.Text = "Match Aspect"; + MatchAspectButton.UseVisualStyleBackColor = true; + MatchAspectButton.Click += MatchAspectButton_Click; + // + // ResetButton + // + ResetButton.Location = new System.Drawing.Point(517, 12); + ResetButton.Name = "ResetButton"; + ResetButton.Size = new System.Drawing.Size(150, 46); + ResetButton.TabIndex = 2; + ResetButton.Text = "Reset"; + ResetButton.UseVisualStyleBackColor = true; + ResetButton.Click += ResetButton_Click; + // + // NormalizeButton + // + NormalizeButton.Location = new System.Drawing.Point(361, 12); + NormalizeButton.Name = "NormalizeButton"; + NormalizeButton.Size = new System.Drawing.Size(150, 46); + NormalizeButton.TabIndex = 3; + NormalizeButton.Text = "Normalize"; + NormalizeButton.UseVisualStyleBackColor = true; + NormalizeButton.Click += NormalizeButton_Click; + // // SetZoomForm // AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; ClientSize = new System.Drawing.Size(800, 450); + Controls.Add(NormalizeButton); + Controls.Add(ResetButton); + Controls.Add(MatchAspectButton); Controls.Add(EnableBoxSelect); FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; Name = "SetZoomForm"; @@ -56,5 +92,8 @@ #endregion private System.Windows.Forms.Button EnableBoxSelect; + private System.Windows.Forms.Button MatchAspectButton; + private System.Windows.Forms.Button ResetButton; + private System.Windows.Forms.Button NormalizeButton; } } \ No newline at end of file diff --git a/Base/Forms/SetZoomForm.cs b/Base/Forms/SetZoomForm.cs index 3a8c499..d13e6af 100644 --- a/Base/Forms/SetZoomForm.cs +++ b/Base/Forms/SetZoomForm.cs @@ -13,6 +13,8 @@ public partial class SetZoomForm : Form { InitializeComponent(); this.refForm = refForm; + + refForm.OnZoomLevelChanged += (o, e) => RedeclareValues(); } private void EnableBoxSelect_Click(object? sender, EventArgs e) @@ -30,9 +32,46 @@ public partial class SetZoomForm : Form EnableBoxSelect.Text = "Box Select"; } } + private void MatchAspectButton_Click(object? sender, EventArgs e) + { + double zoomXFactor = refForm.ZoomLevel.x / refForm.ZoomLevel.y; + double actualXFactor = refForm.ClientRectangle.Width / refForm.ClientRectangle.Height; + + double diff = actualXFactor / zoomXFactor; + int newWidth = (int)(refForm.Width / diff); + refForm.ZoomLevel = new(refForm.ZoomLevel.x * diff, refForm.ZoomLevel.y); + + int maxScreenWidth = Screen.FromControl(refForm).WorkingArea.Width; + if (newWidth >= maxScreenWidth) + { + refForm.Location = new(0, refForm.Location.Y); + + double xScaleFactor = (double)maxScreenWidth / newWidth; + newWidth = maxScreenWidth; + refForm.Height = (int)(refForm.Height * xScaleFactor); + refForm.ZoomLevel = new(refForm.ZoomLevel.x * xScaleFactor, refForm.ZoomLevel.y * xScaleFactor); + } + + refForm.Width = newWidth; + } + private void ResetButton_Click(object? sender, EventArgs e) + { + refForm.ResetAllViewport(); + } + + private void RedeclareValues() + { + Invalidate(false); + } internal void CompleteBoxSelection() { if (boxSelectEnabled) EnableBoxSelect_Click(null, new()); } + + private void NormalizeButton_Click(object sender, EventArgs e) + { + double factor = 1 / Math.Min(refForm.ZoomLevel.x, refForm.ZoomLevel.y); + refForm.ZoomLevel = new(factor * refForm.ZoomLevel.x, factor * refForm.ZoomLevel.y); + } } -- 2.49.0.windows.1 From ed4ce2bbeb840da59c74b35999ae83f137444437 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Sat, 20 Apr 2024 09:46:23 -0400 Subject: [PATCH 14/21] More zoom UI progress. --- Base/Forms/GraphForm.Designer.cs | 33 +++++----- Base/Forms/GraphForm.cs | 33 +++++++++- Base/Forms/SetZoomForm.Designer.cs | 99 +++++++++++++++++++++++++++--- Base/Forms/SetZoomForm.cs | 34 ++++++++-- 4 files changed, 167 insertions(+), 32 deletions(-) diff --git a/Base/Forms/GraphForm.Designer.cs b/Base/Forms/GraphForm.Designer.cs index 722588b..54a8122 100644 --- a/Base/Forms/GraphForm.Designer.cs +++ b/Base/Forms/GraphForm.Designer.cs @@ -47,23 +47,22 @@ namespace Graphing.Forms MenuOperationsTranslate = new ToolStripMenuItem(); MenuConvert = new ToolStripMenuItem(); MenuConvertEquation = new ToolStripMenuItem(); + MenuConvertSlopeField = new ToolStripMenuItem(); MenuMisc = new ToolStripMenuItem(); MenuMiscCaches = new ToolStripMenuItem(); MiscMenuPreload = new ToolStripMenuItem(); - MenuConvertSlopeField = new ToolStripMenuItem(); GraphMenu.SuspendLayout(); SuspendLayout(); // // ResetViewportButton // ResetViewportButton.Anchor = AnchorStyles.Top | AnchorStyles.Right; - ResetViewportButton.Font = new Font("Segoe UI Emoji", 13.875F, FontStyle.Regular, GraphicsUnit.Point, 0); + ResetViewportButton.Font = new Font("Segoe UI Emoji", 12F, FontStyle.Regular, GraphicsUnit.Point, 0); ResetViewportButton.Location = new Point(1373, 43); ResetViewportButton.Name = "ResetViewportButton"; ResetViewportButton.Size = new Size(64, 64); ResetViewportButton.TabIndex = 0; - ResetViewportButton.Text = "⌂"; - ResetViewportButton.TextAlign = ContentAlignment.TopRight; + ResetViewportButton.Text = "🏠"; ResetViewportButton.UseVisualStyleBackColor = true; ResetViewportButton.Click += ResetViewportButton_Click; // @@ -73,7 +72,7 @@ namespace Graphing.Forms GraphMenu.Items.AddRange(new ToolStripItem[] { MenuViewport, MenuElements, MenuOperations, MenuConvert, MenuMisc }); GraphMenu.Location = new Point(0, 0); GraphMenu.Name = "GraphMenu"; - GraphMenu.Size = new Size(1449, 42); + GraphMenu.Size = new Size(1449, 40); GraphMenu.TabIndex = 1; GraphMenu.Text = "menuStrip1"; // @@ -81,7 +80,7 @@ namespace Graphing.Forms // MenuViewport.DropDownItems.AddRange(new ToolStripItem[] { ButtonViewportSetZoom, ButtonViewportSetCenter, ButtonViewportReset, ButtonViewportResetWindow }); MenuViewport.Name = "MenuViewport"; - MenuViewport.Size = new Size(129, 38); + MenuViewport.Size = new Size(129, 36); MenuViewport.Text = "Viewport"; // // ButtonViewportSetZoom @@ -116,7 +115,7 @@ namespace Graphing.Forms // MenuElements.DropDownItems.AddRange(new ToolStripItem[] { MenuElementsColors, MenuElementsRemove }); MenuElements.Name = "MenuElements"; - MenuElements.Size = new Size(131, 38); + MenuElements.Size = new Size(131, 36); MenuElements.Text = "Elements"; // // MenuElementsColors @@ -135,7 +134,7 @@ namespace Graphing.Forms // MenuOperations.DropDownItems.AddRange(new ToolStripItem[] { MenuOperationsDerivative, MenuOperationsIntegral, MenuOperationsTranslate }); MenuOperations.Name = "MenuOperations"; - MenuOperations.Size = new Size(151, 38); + MenuOperations.Size = new Size(151, 36); MenuOperations.Text = "Operations"; // // MenuOperationsDerivative @@ -160,20 +159,26 @@ namespace Graphing.Forms // MenuConvert.DropDownItems.AddRange(new ToolStripItem[] { MenuConvertEquation, MenuConvertSlopeField }); MenuConvert.Name = "MenuConvert"; - MenuConvert.Size = new Size(118, 38); + MenuConvert.Size = new Size(118, 36); MenuConvert.Text = "Convert"; // // MenuConvertEquation // MenuConvertEquation.Name = "MenuConvertEquation"; - MenuConvertEquation.Size = new Size(359, 44); + MenuConvertEquation.Size = new Size(297, 44); MenuConvertEquation.Text = "To Equation"; // + // MenuConvertSlopeField + // + MenuConvertSlopeField.Name = "MenuConvertSlopeField"; + MenuConvertSlopeField.Size = new Size(297, 44); + MenuConvertSlopeField.Text = "To Slope Field"; + // // MenuMisc // MenuMisc.DropDownItems.AddRange(new ToolStripItem[] { MenuMiscCaches, MiscMenuPreload }); MenuMisc.Name = "MenuMisc"; - MenuMisc.Size = new Size(83, 38); + MenuMisc.Size = new Size(83, 36); MenuMisc.Text = "Misc"; // // MenuMiscCaches @@ -190,12 +195,6 @@ namespace Graphing.Forms MiscMenuPreload.Text = "Preload Cache"; MiscMenuPreload.Click += MiscMenuPreload_Click; // - // MenuConvertSlopeField - // - MenuConvertSlopeField.Name = "MenuConvertSlopeField"; - MenuConvertSlopeField.Size = new Size(359, 44); - MenuConvertSlopeField.Text = "To Slope Field"; - // // GraphForm // AutoScaleDimensions = new SizeF(13F, 32F); diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 211ea2c..190b983 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -41,6 +41,29 @@ public partial class GraphForm : Form } private Float2 _zoomLevel; + public bool ViewportLocked + { + get => _viewportLocked; + set + { + if (value) + { + FormBorderStyle = FormBorderStyle.FixedSingle; + ResetViewportButton.Text = "🔒"; + } + else + { + FormBorderStyle = FormBorderStyle.Sizable; + ResetViewportButton.Text = "🏠"; + } + MaximizeBox = !value; + ResetViewportButton.Enabled = !value; + + _viewportLocked = value; + } + } + private bool _viewportLocked; + private readonly Point initialWindowPos; private readonly Size initialWindowSize; @@ -318,7 +341,7 @@ public partial class GraphForm : Form if (selectState == SelectionState.GraphSelect) Invalidate(false); } - if (selectState == SelectionState.None) + if (selectState == SelectionState.None && !ViewportLocked) { selectState = SelectionState.ViewportDrag; initialMouseLocation = new Int2(Cursor.Position.X, Cursor.Position.Y); @@ -327,7 +350,8 @@ public partial class GraphForm : Form } protected override void OnMouseUp(MouseEventArgs e) { - if (selectState == SelectionState.ViewportDrag) + if (selectState == SelectionState.None) return; + else if (selectState == SelectionState.ViewportDrag) { Int2 pixelDiff = new(initialMouseLocation.x - Cursor.Position.X, initialMouseLocation.y - Cursor.Position.Y); @@ -365,7 +389,8 @@ public partial class GraphForm : Form } protected override void OnMouseMove(MouseEventArgs e) { - if (selectState == SelectionState.ViewportDrag) + if (selectState == SelectionState.None) return; + else if (selectState == SelectionState.ViewportDrag) { Int2 pixelDiff = new(initialMouseLocation.x - Cursor.Position.X, initialMouseLocation.y - Cursor.Position.Y); @@ -384,6 +409,8 @@ public partial class GraphForm : Form } protected override void OnMouseWheel(MouseEventArgs e) { + if (ViewportLocked) return; + Float2 newZoom = ZoomLevel; newZoom.x *= 1 - e.Delta * 0.00075; // Zoom factor. newZoom.y *= 1 - e.Delta * 0.00075; diff --git a/Base/Forms/SetZoomForm.Designer.cs b/Base/Forms/SetZoomForm.Designer.cs index c8d464b..3d8fdaa 100644 --- a/Base/Forms/SetZoomForm.Designer.cs +++ b/Base/Forms/SetZoomForm.Designer.cs @@ -32,13 +32,20 @@ MatchAspectButton = new System.Windows.Forms.Button(); ResetButton = new System.Windows.Forms.Button(); NormalizeButton = new System.Windows.Forms.Button(); + MinBoxX = new System.Windows.Forms.TextBox(); + TextX = new System.Windows.Forms.Label(); + MaxBoxX = new System.Windows.Forms.TextBox(); + MaxBoxY = new System.Windows.Forms.TextBox(); + TextY = new System.Windows.Forms.Label(); + MinBoxY = new System.Windows.Forms.TextBox(); + ViewportLock = new System.Windows.Forms.CheckBox(); SuspendLayout(); // // EnableBoxSelect // EnableBoxSelect.Location = new System.Drawing.Point(12, 12); EnableBoxSelect.Name = "EnableBoxSelect"; - EnableBoxSelect.Size = new System.Drawing.Size(150, 46); + EnableBoxSelect.Size = new System.Drawing.Size(187, 46); EnableBoxSelect.TabIndex = 0; EnableBoxSelect.Text = "Box Select"; EnableBoxSelect.UseVisualStyleBackColor = true; @@ -46,7 +53,7 @@ // // MatchAspectButton // - MatchAspectButton.Location = new System.Drawing.Point(168, 12); + MatchAspectButton.Location = new System.Drawing.Point(12, 64); MatchAspectButton.Name = "MatchAspectButton"; MatchAspectButton.Size = new System.Drawing.Size(187, 46); MatchAspectButton.TabIndex = 1; @@ -56,9 +63,9 @@ // // ResetButton // - ResetButton.Location = new System.Drawing.Point(517, 12); + ResetButton.Location = new System.Drawing.Point(12, 168); ResetButton.Name = "ResetButton"; - ResetButton.Size = new System.Drawing.Size(150, 46); + ResetButton.Size = new System.Drawing.Size(187, 46); ResetButton.TabIndex = 2; ResetButton.Text = "Reset"; ResetButton.UseVisualStyleBackColor = true; @@ -66,19 +73,89 @@ // // NormalizeButton // - NormalizeButton.Location = new System.Drawing.Point(361, 12); + NormalizeButton.Location = new System.Drawing.Point(12, 116); NormalizeButton.Name = "NormalizeButton"; - NormalizeButton.Size = new System.Drawing.Size(150, 46); + NormalizeButton.Size = new System.Drawing.Size(187, 46); NormalizeButton.TabIndex = 3; NormalizeButton.Text = "Normalize"; NormalizeButton.UseVisualStyleBackColor = true; NormalizeButton.Click += NormalizeButton_Click; // + // MinBoxX + // + MinBoxX.Location = new System.Drawing.Point(227, 49); + MinBoxX.Margin = new System.Windows.Forms.Padding(25, 3, 0, 3); + MinBoxX.Name = "MinBoxX"; + MinBoxX.Size = new System.Drawing.Size(108, 39); + MinBoxX.TabIndex = 4; + // + // TextX + // + TextX.Location = new System.Drawing.Point(335, 49); + TextX.Margin = new System.Windows.Forms.Padding(0); + TextX.Name = "TextX"; + TextX.Size = new System.Drawing.Size(77, 39); + TextX.TabIndex = 5; + TextX.Text = "≤ x ≤"; + TextX.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // MaxBoxX + // + MaxBoxX.Location = new System.Drawing.Point(412, 49); + MaxBoxX.Margin = new System.Windows.Forms.Padding(0, 3, 25, 3); + MaxBoxX.Name = "MaxBoxX"; + MaxBoxX.Size = new System.Drawing.Size(108, 39); + MaxBoxX.TabIndex = 6; + // + // MaxBoxY + // + MaxBoxY.Location = new System.Drawing.Point(412, 94); + MaxBoxY.Margin = new System.Windows.Forms.Padding(0, 3, 25, 3); + MaxBoxY.Name = "MaxBoxY"; + MaxBoxY.Size = new System.Drawing.Size(108, 39); + MaxBoxY.TabIndex = 9; + // + // TextY + // + TextY.Location = new System.Drawing.Point(335, 94); + TextY.Margin = new System.Windows.Forms.Padding(0); + TextY.Name = "TextY"; + TextY.Size = new System.Drawing.Size(77, 39); + TextY.TabIndex = 8; + TextY.Text = "≤ y ≤"; + TextY.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // MinBoxY + // + MinBoxY.Location = new System.Drawing.Point(227, 94); + MinBoxY.Margin = new System.Windows.Forms.Padding(25, 3, 0, 3); + MinBoxY.Name = "MinBoxY"; + MinBoxY.Size = new System.Drawing.Size(108, 39); + MinBoxY.TabIndex = 7; + // + // ViewportLock + // + ViewportLock.Location = new System.Drawing.Point(227, 139); + ViewportLock.Name = "ViewportLock"; + ViewportLock.Size = new System.Drawing.Size(293, 39); + ViewportLock.TabIndex = 10; + ViewportLock.Text = "Lock Viewport"; + ViewportLock.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + ViewportLock.UseVisualStyleBackColor = true; + ViewportLock.CheckedChanged += ViewportLock_CheckedChanged; + // // SetZoomForm // AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - ClientSize = new System.Drawing.Size(800, 450); + ClientSize = new System.Drawing.Size(533, 227); + Controls.Add(ViewportLock); + Controls.Add(MaxBoxY); + Controls.Add(TextY); + Controls.Add(MinBoxY); + Controls.Add(MaxBoxX); + Controls.Add(TextX); + Controls.Add(MinBoxX); Controls.Add(NormalizeButton); Controls.Add(ResetButton); Controls.Add(MatchAspectButton); @@ -87,6 +164,7 @@ Name = "SetZoomForm"; Text = "Set Viewport Zoom"; ResumeLayout(false); + PerformLayout(); } #endregion @@ -95,5 +173,12 @@ private System.Windows.Forms.Button MatchAspectButton; private System.Windows.Forms.Button ResetButton; private System.Windows.Forms.Button NormalizeButton; + private System.Windows.Forms.TextBox MinBoxX; + private System.Windows.Forms.Label TextX; + private System.Windows.Forms.TextBox MaxBoxX; + private System.Windows.Forms.TextBox MaxBoxY; + private System.Windows.Forms.Label TextY; + private System.Windows.Forms.TextBox MinBoxY; + private System.Windows.Forms.CheckBox ViewportLock; } } \ No newline at end of file diff --git a/Base/Forms/SetZoomForm.cs b/Base/Forms/SetZoomForm.cs index d13e6af..cc7c83b 100644 --- a/Base/Forms/SetZoomForm.cs +++ b/Base/Forms/SetZoomForm.cs @@ -14,7 +14,8 @@ public partial class SetZoomForm : Form InitializeComponent(); this.refForm = refForm; - refForm.OnZoomLevelChanged += (o, e) => RedeclareValues(); + refForm.Paint += (o, e) => RedeclareValues(); + RedeclareValues(); } private void EnableBoxSelect_Click(object? sender, EventArgs e) @@ -54,6 +55,11 @@ public partial class SetZoomForm : Form refForm.Width = newWidth; } + private void NormalizeButton_Click(object? sender, EventArgs e) + { + double factor = 1 / Math.Min(refForm.ZoomLevel.x, refForm.ZoomLevel.y); + refForm.ZoomLevel = new(factor * refForm.ZoomLevel.x, factor * refForm.ZoomLevel.y); + } private void ResetButton_Click(object? sender, EventArgs e) { refForm.ResetAllViewport(); @@ -61,7 +67,25 @@ public partial class SetZoomForm : Form private void RedeclareValues() { - Invalidate(false); + bool enabled = !refForm.ViewportLocked; + + Float2 minGraph = refForm.MinVisibleGraph, + maxGraph = refForm.MaxVisibleGraph; + + MinBoxX.Text = $"{minGraph.x:0.000}"; + MaxBoxX.Text = $"{maxGraph.x:0.000}"; + MinBoxY.Text = $"{minGraph.y:0.000}"; + MaxBoxY.Text = $"{maxGraph.y:0.000}"; + + ViewportLock.Checked = !enabled; + EnableBoxSelect.Enabled = enabled; + MatchAspectButton.Enabled = enabled; + NormalizeButton.Enabled = enabled; + ResetButton.Enabled = enabled; + MinBoxX.Enabled = enabled; + MaxBoxX.Enabled = enabled; + MinBoxY.Enabled = enabled; + MaxBoxY.Enabled = enabled; } internal void CompleteBoxSelection() @@ -69,9 +93,9 @@ public partial class SetZoomForm : Form if (boxSelectEnabled) EnableBoxSelect_Click(null, new()); } - private void NormalizeButton_Click(object sender, EventArgs e) + private void ViewportLock_CheckedChanged(object? sender, EventArgs e) { - double factor = 1 / Math.Min(refForm.ZoomLevel.x, refForm.ZoomLevel.y); - refForm.ZoomLevel = new(factor * refForm.ZoomLevel.x, factor * refForm.ZoomLevel.y); + refForm.ViewportLocked = ViewportLock.Checked; + RedeclareValues(); } } -- 2.49.0.windows.1 From aba32f8b58122b737c7c450c55eaa0b43a4aac56 Mon Sep 17 00:00:00 2001 From: That-One-Nerd Date: Sat, 20 Apr 2024 18:02:17 -0400 Subject: [PATCH 15/21] Input options now work. I think I can call it done for the zooming system. --- Base/Forms/GraphForm.cs | 2 +- Base/Forms/SetZoomForm.cs | 96 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 190b983..47361f8 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -23,7 +23,7 @@ public partial class GraphForm : Form public static readonly Color UnitsTextColor = Color.Black; public static readonly Color ZoomBoxColor = Color.Black; - public Float2 ScreenCenter { get; private set; } + public Float2 ScreenCenter { get; set; } public Float2 Dpi { get; private set; } public float DpiFloat { get; private set; } diff --git a/Base/Forms/SetZoomForm.cs b/Base/Forms/SetZoomForm.cs index cc7c83b..231929c 100644 --- a/Base/Forms/SetZoomForm.cs +++ b/Base/Forms/SetZoomForm.cs @@ -16,6 +16,28 @@ public partial class SetZoomForm : Form refForm.Paint += (o, e) => RedeclareValues(); RedeclareValues(); + + MinBoxX.Leave += MinBoxX_Finish; + MinBoxX.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) MinBoxX_Finish(o, e); + }; + MaxBoxX.Leave += MaxBoxX_Finish; + MaxBoxX.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) MaxBoxX_Finish(o, e); + }; + + MinBoxY.Leave += MinBoxY_Finish; + MinBoxY.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) MinBoxY_Finish(o, e); + }; + MaxBoxY.Leave += MaxBoxY_Finish; + MaxBoxY.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) MaxBoxY_Finish(o, e); + }; } private void EnableBoxSelect_Click(object? sender, EventArgs e) @@ -64,8 +86,74 @@ public partial class SetZoomForm : Form { refForm.ResetAllViewport(); } + private void ViewportLock_CheckedChanged(object? sender, EventArgs e) + { + refForm.ViewportLocked = ViewportLock.Checked; + RedeclareValues(); + } - private void RedeclareValues() + private void MinBoxX_Finish(object? sender, EventArgs e) + { + if (double.TryParse(MinBoxX.Text, out double minX)) + { + Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph; + + double newCenterX = (minX + max.x) / 2, + zoomFactorX = (max.x - minX) / (max.x - min.x); + + refForm.ScreenCenter = new(newCenterX, refForm.ScreenCenter.y); + refForm.ZoomLevel = new(refForm.ZoomLevel.x * zoomFactorX, refForm.ZoomLevel.y); + } + + refForm.Invalidate(false); + } + private void MaxBoxX_Finish(object? sender, EventArgs e) + { + if (double.TryParse(MaxBoxX.Text, out double maxX)) + { + Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph; + + double newCenterX = (min.x + maxX) / 2, + zoomFactorX = (maxX - min.x) / (max.x - min.x); + + refForm.ScreenCenter = new(newCenterX, refForm.ScreenCenter.y); + refForm.ZoomLevel = new(refForm.ZoomLevel.x * zoomFactorX, refForm.ZoomLevel.y); + } + + refForm.Invalidate(false); + } + private void MinBoxY_Finish(object? sender, EventArgs e) + { + if (double.TryParse(MinBoxY.Text, out double minY)) + { + Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph; + + double newCenterY = -(minY + max.y) / 2, // Keeping it positive flips it for some reason ??? + zoomFactorY = (max.y - minY) / (max.y - min.y); + + refForm.ScreenCenter = new(refForm.ScreenCenter.x, newCenterY); + refForm.ZoomLevel = new(refForm.ZoomLevel.x, refForm.ZoomLevel.y * zoomFactorY); + } + + refForm.Invalidate(false); + } + private void MaxBoxY_Finish(object? sender, EventArgs e) + { + if (double.TryParse(MaxBoxY.Text, out double maxY)) + { + Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph; + + double newCenterY = -(min.y + maxY) / 2, // Keeping it positive flips it for some reason ??? + zoomFactorY = (maxY - min.y) / (max.y - min.y); + + refForm.ScreenCenter = new(refForm.ScreenCenter.x, newCenterY); + refForm.ZoomLevel = new(refForm.ZoomLevel.x, refForm.ZoomLevel.y * zoomFactorY); + } + + refForm.Invalidate(false); + } + + public void RedeclareValues() { bool enabled = !refForm.ViewportLocked; @@ -92,10 +180,4 @@ public partial class SetZoomForm : Form { if (boxSelectEnabled) EnableBoxSelect_Click(null, new()); } - - private void ViewportLock_CheckedChanged(object? sender, EventArgs e) - { - refForm.ViewportLocked = ViewportLock.Checked; - RedeclareValues(); - } } -- 2.49.0.windows.1 From 789a3b448efa00ec678dfc4aeab0c2ea0ecf12eb Mon Sep 17 00:00:00 2001 From: That-One-Nerd Date: Sun, 21 Apr 2024 09:04:35 -0400 Subject: [PATCH 16/21] Update checker is mostly done. Negative zoom levels are no longer permitted either. --- Base/Forms/GraphForm.Designer.cs | 107 ++++++++++++++++++++++++------- Base/Forms/GraphForm.cs | 81 ++++++++++------------- Base/Forms/SetZoomForm.cs | 44 +++++++++++++ Base/Helpers/UpdaterHelper.cs | 83 ------------------------ 4 files changed, 163 insertions(+), 152 deletions(-) delete mode 100644 Base/Helpers/UpdaterHelper.cs diff --git a/Base/Forms/GraphForm.Designer.cs b/Base/Forms/GraphForm.Designer.cs index 54a8122..2de0a19 100644 --- a/Base/Forms/GraphForm.Designer.cs +++ b/Base/Forms/GraphForm.Designer.cs @@ -51,16 +51,22 @@ namespace Graphing.Forms MenuMisc = new ToolStripMenuItem(); MenuMiscCaches = new ToolStripMenuItem(); MiscMenuPreload = new ToolStripMenuItem(); + UpdaterPopup = new Panel(); + UpdaterPopupDownloadButton = new Button(); + UpdaterPopupCloseButton = new Button(); + UpdaterPopupMessage = new Label(); GraphMenu.SuspendLayout(); + UpdaterPopup.SuspendLayout(); SuspendLayout(); // // ResetViewportButton // ResetViewportButton.Anchor = AnchorStyles.Top | AnchorStyles.Right; ResetViewportButton.Font = new Font("Segoe UI Emoji", 12F, FontStyle.Regular, GraphicsUnit.Point, 0); - ResetViewportButton.Location = new Point(1373, 43); + ResetViewportButton.Location = new Point(739, 20); + ResetViewportButton.Margin = new Padding(2, 1, 2, 1); ResetViewportButton.Name = "ResetViewportButton"; - ResetViewportButton.Size = new Size(64, 64); + ResetViewportButton.Size = new Size(34, 30); ResetViewportButton.TabIndex = 0; ResetViewportButton.Text = "🏠"; ResetViewportButton.UseVisualStyleBackColor = true; @@ -72,7 +78,8 @@ namespace Graphing.Forms GraphMenu.Items.AddRange(new ToolStripItem[] { MenuViewport, MenuElements, MenuOperations, MenuConvert, MenuMisc }); GraphMenu.Location = new Point(0, 0); GraphMenu.Name = "GraphMenu"; - GraphMenu.Size = new Size(1449, 40); + GraphMenu.Padding = new Padding(3, 1, 0, 1); + GraphMenu.Size = new Size(780, 24); GraphMenu.TabIndex = 1; GraphMenu.Text = "menuStrip1"; // @@ -80,34 +87,34 @@ namespace Graphing.Forms // MenuViewport.DropDownItems.AddRange(new ToolStripItem[] { ButtonViewportSetZoom, ButtonViewportSetCenter, ButtonViewportReset, ButtonViewportResetWindow }); MenuViewport.Name = "MenuViewport"; - MenuViewport.Size = new Size(129, 36); + MenuViewport.Size = new Size(66, 22); MenuViewport.Text = "Viewport"; // // ButtonViewportSetZoom // ButtonViewportSetZoom.Name = "ButtonViewportSetZoom"; - ButtonViewportSetZoom.Size = new Size(350, 44); + ButtonViewportSetZoom.Size = new Size(174, 22); ButtonViewportSetZoom.Text = "Set Zoom"; ButtonViewportSetZoom.Click += ButtonViewportSetZoom_Click; // // ButtonViewportSetCenter // ButtonViewportSetCenter.Name = "ButtonViewportSetCenter"; - ButtonViewportSetCenter.Size = new Size(350, 44); + ButtonViewportSetCenter.Size = new Size(174, 22); ButtonViewportSetCenter.Text = "Set Center Position"; ButtonViewportSetCenter.Click += ButtonViewportSetCenter_Click; // // ButtonViewportReset // ButtonViewportReset.Name = "ButtonViewportReset"; - ButtonViewportReset.Size = new Size(350, 44); + ButtonViewportReset.Size = new Size(174, 22); ButtonViewportReset.Text = "Reset Viewport"; ButtonViewportReset.Click += ButtonViewportReset_Click; // // ButtonViewportResetWindow // ButtonViewportResetWindow.Name = "ButtonViewportResetWindow"; - ButtonViewportResetWindow.Size = new Size(350, 44); + ButtonViewportResetWindow.Size = new Size(174, 22); ButtonViewportResetWindow.Text = "Reset Window Size"; ButtonViewportResetWindow.Click += ButtonViewportResetWindow_Click; // @@ -115,98 +122,148 @@ namespace Graphing.Forms // MenuElements.DropDownItems.AddRange(new ToolStripItem[] { MenuElementsColors, MenuElementsRemove }); MenuElements.Name = "MenuElements"; - MenuElements.Size = new Size(131, 36); + MenuElements.Size = new Size(67, 22); MenuElements.Text = "Elements"; // // MenuElementsColors // MenuElementsColors.Name = "MenuElementsColors"; - MenuElementsColors.Size = new Size(233, 44); + MenuElementsColors.Size = new Size(117, 22); MenuElementsColors.Text = "Colors"; // // MenuElementsRemove // MenuElementsRemove.Name = "MenuElementsRemove"; - MenuElementsRemove.Size = new Size(233, 44); + MenuElementsRemove.Size = new Size(117, 22); MenuElementsRemove.Text = "Remove"; // // MenuOperations // MenuOperations.DropDownItems.AddRange(new ToolStripItem[] { MenuOperationsDerivative, MenuOperationsIntegral, MenuOperationsTranslate }); MenuOperations.Name = "MenuOperations"; - MenuOperations.Size = new Size(151, 36); + MenuOperations.Size = new Size(77, 22); MenuOperations.Text = "Operations"; // // MenuOperationsDerivative // MenuOperationsDerivative.Name = "MenuOperationsDerivative"; - MenuOperationsDerivative.Size = new Size(360, 44); + MenuOperationsDerivative.Size = new Size(179, 22); MenuOperationsDerivative.Text = "Compute Derivative"; // // MenuOperationsIntegral // MenuOperationsIntegral.Name = "MenuOperationsIntegral"; - MenuOperationsIntegral.Size = new Size(360, 44); + MenuOperationsIntegral.Size = new Size(179, 22); MenuOperationsIntegral.Text = "Compute Integral"; // // MenuOperationsTranslate // MenuOperationsTranslate.Name = "MenuOperationsTranslate"; - MenuOperationsTranslate.Size = new Size(360, 44); + MenuOperationsTranslate.Size = new Size(179, 22); MenuOperationsTranslate.Text = "Translate"; // // MenuConvert // MenuConvert.DropDownItems.AddRange(new ToolStripItem[] { MenuConvertEquation, MenuConvertSlopeField }); MenuConvert.Name = "MenuConvert"; - MenuConvert.Size = new Size(118, 36); + MenuConvert.Size = new Size(61, 22); MenuConvert.Text = "Convert"; // // MenuConvertEquation // MenuConvertEquation.Name = "MenuConvertEquation"; - MenuConvertEquation.Size = new Size(297, 44); + MenuConvertEquation.Size = new Size(146, 22); MenuConvertEquation.Text = "To Equation"; // // MenuConvertSlopeField // MenuConvertSlopeField.Name = "MenuConvertSlopeField"; - MenuConvertSlopeField.Size = new Size(297, 44); + MenuConvertSlopeField.Size = new Size(146, 22); MenuConvertSlopeField.Text = "To Slope Field"; // // MenuMisc // MenuMisc.DropDownItems.AddRange(new ToolStripItem[] { MenuMiscCaches, MiscMenuPreload }); MenuMisc.Name = "MenuMisc"; - MenuMisc.Size = new Size(83, 36); + MenuMisc.Size = new Size(44, 22); MenuMisc.Text = "Misc"; // // MenuMiscCaches // MenuMiscCaches.Name = "MenuMiscCaches"; - MenuMiscCaches.Size = new Size(299, 44); + MenuMiscCaches.Size = new Size(150, 22); MenuMiscCaches.Text = "View Caches"; MenuMiscCaches.Click += MenuMiscCaches_Click; // // MiscMenuPreload // MiscMenuPreload.Name = "MiscMenuPreload"; - MiscMenuPreload.Size = new Size(299, 44); + MiscMenuPreload.Size = new Size(150, 22); MiscMenuPreload.Text = "Preload Cache"; MiscMenuPreload.Click += MiscMenuPreload_Click; // + // UpdaterPopup + // + UpdaterPopup.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + UpdaterPopup.BackColor = SystemColors.HighlightText; + UpdaterPopup.BorderStyle = BorderStyle.FixedSingle; + UpdaterPopup.Controls.Add(UpdaterPopupDownloadButton); + UpdaterPopup.Controls.Add(UpdaterPopupCloseButton); + UpdaterPopup.Controls.Add(UpdaterPopupMessage); + UpdaterPopup.Location = new Point(520, 371); + UpdaterPopup.Name = "UpdaterPopup"; + UpdaterPopup.Size = new Size(261, 55); + UpdaterPopup.TabIndex = 2; + UpdaterPopup.Visible = false; + // + // UpdaterPopupDownloadButton + // + UpdaterPopupDownloadButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + UpdaterPopupDownloadButton.Location = new Point(181, 27); + UpdaterPopupDownloadButton.Name = "UpdaterPopupDownloadButton"; + UpdaterPopupDownloadButton.Size = new Size(75, 23); + UpdaterPopupDownloadButton.TabIndex = 2; + UpdaterPopupDownloadButton.Text = "Visit"; + UpdaterPopupDownloadButton.UseVisualStyleBackColor = true; + // + // UpdaterPopupCloseButton + // + UpdaterPopupCloseButton.Anchor = AnchorStyles.Top | AnchorStyles.Right; + UpdaterPopupCloseButton.Location = new Point(234, 1); + UpdaterPopupCloseButton.Margin = new Padding(1); + UpdaterPopupCloseButton.Name = "UpdaterPopupCloseButton"; + UpdaterPopupCloseButton.Size = new Size(24, 24); + UpdaterPopupCloseButton.TabIndex = 1; + UpdaterPopupCloseButton.Text = "X"; + UpdaterPopupCloseButton.UseVisualStyleBackColor = true; + UpdaterPopupCloseButton.Click += UpdaterPopupCloseButton_Click; + // + // UpdaterPopupMessage + // + UpdaterPopupMessage.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left; + UpdaterPopupMessage.Font = new Font("Segoe UI", 9.75F, FontStyle.Bold, GraphicsUnit.Point, 0); + UpdaterPopupMessage.Location = new Point(3, 3); + UpdaterPopupMessage.Margin = new Padding(3); + UpdaterPopupMessage.Name = "UpdaterPopupMessage"; + UpdaterPopupMessage.Size = new Size(228, 47); + UpdaterPopupMessage.TabIndex = 0; + UpdaterPopupMessage.Text = "A update is available!\r\nA.B.C → E.F.G"; + // // GraphForm // - AutoScaleDimensions = new SizeF(13F, 32F); + AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(1449, 907); + ClientSize = new Size(780, 425); + Controls.Add(UpdaterPopup); Controls.Add(ResetViewportButton); Controls.Add(GraphMenu); MainMenuStrip = GraphMenu; + Margin = new Padding(2, 1, 2, 1); Name = "GraphForm"; Text = "GraphFormBase"; GraphMenu.ResumeLayout(false); GraphMenu.PerformLayout(); + UpdaterPopup.ResumeLayout(false); ResumeLayout(false); PerformLayout(); } @@ -233,5 +290,9 @@ namespace Graphing.Forms private ToolStripMenuItem MenuElementsRemove; private ToolStripMenuItem MenuOperationsTranslate; private ToolStripMenuItem MenuConvertSlopeField; + private Panel UpdaterPopup; + private Label UpdaterPopupMessage; + private Button UpdaterPopupCloseButton; + private Button UpdaterPopupDownloadButton; } } \ No newline at end of file diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 47361f8..df116d7 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -1,8 +1,7 @@ using Graphing.Abstract; -using Graphing.Helpers; -using Graphing.Parts; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; @@ -18,11 +17,14 @@ public partial class GraphForm : Form { public static readonly Color BackgroundColor = Color.White; public static readonly Color MainAxisColor = Color.Black; - public static readonly Color SemiAxisColor = Color.FromArgb(unchecked((int)0xFF_999999)); - public static readonly Color QuarterAxisColor = Color.FromArgb(unchecked((int)0xFF_E0E0E0)); + public static readonly Color SemiAxisColor = Color.FromArgb(unchecked((int)0xFF_999999)); // Grayish + public static readonly Color QuarterAxisColor = Color.FromArgb(unchecked((int)0xFF_E0E0E0)); // Lighter grayish public static readonly Color UnitsTextColor = Color.Black; public static readonly Color ZoomBoxColor = Color.Black; + public static readonly Color MajorUpdateColor = Color.FromArgb(unchecked((int)0xFF_F74434)); // Red + public static readonly Color MinorUpdateColor = Color.FromArgb(unchecked((int)0xFF_FCA103)); // Orange + public Float2 ScreenCenter { get; set; } public Float2 Dpi { get; private set; } @@ -630,6 +632,10 @@ public partial class GraphForm : Form foreach (Graphable able in Graphables) able.Preload(xRange, yRange, step); Invalidate(false); } + private void UpdaterPopupCloseButton_Click(object? sender, EventArgs e) + { + UpdaterPopup.Dispose(); + } private void ElementsOperationsTranslate_Click(Graphable ableRaw, ITranslatable ableTrans) { @@ -646,7 +652,7 @@ public partial class GraphForm : Form shifter.Show(); } - private static async void RunUpdateChecker() + private async void RunUpdateChecker() { try { @@ -669,56 +675,39 @@ public partial class GraphForm : Form if (newVersion > curVersion) { - // Updates are required. - DialogResult button = MessageBox.Show( - $"A new update is available!\n{curVersion} -> {newVersion}\nWould you like to download the update?", - "Graphing Calculator Update", MessageBoxButtons.YesNo); + string type; - if (button == DialogResult.No) return; - - string? project = UpdaterHelper.GetProjectPath(); - if (project is null) + if (newVersion.Major > curVersion.Major || // x.0.0 + newVersion.Minor > curVersion.Minor) // 0.x.0 { - MessageBox.Show("Cannot find project root. You'll likely have to update manually.", - "Error running automatic updates.", MessageBoxButtons.OK, - MessageBoxIcon.Error); - return; + type = "major"; + UpdaterPopupMessage.ForeColor = MajorUpdateColor; + } + else // 0.0.x + { + type = "minor"; + UpdaterPopupMessage.ForeColor = MinorUpdateColor; } - if (await UpdaterHelper.UsingNugetPackage(project)) + UpdaterPopupMessage.Text = $"A {type} update is available!\n{curVersion} → {newVersion}"; + UpdaterPopup.Visible = true; + + string url = latest["html_url"]!.GetValue(); + Console.WriteLine($"An update is available! {curVersion} -> {newVersion}\n{url}"); + UpdaterPopupDownloadButton.Click += (o, e) => { - Console.WriteLine($"Attempting to update via the NuGet Package Manager."); - bool status = await UpdaterHelper.UpdateProjectByNuget(latest["tag_name"]!.GetValue(), project); - if (!status) + ProcessStartInfo website = new() { - MessageBox.Show("Failed to update with the NuGet Package Manager. " + - "You'll likely have to update manually.", - "Error running automatic updates.", MessageBoxButtons.OK, - MessageBoxIcon.Error); - return; - } - } - else - { - Console.WriteLine($"Attempting to update via a GitHub asset download."); - bool status = await UpdaterHelper.UpdateProjectByGitHub(latest["tag_name"]!.GetValue(), project); - if (status) - { - MessageBox.Show("Update ready. Restart the project to complete the update."); - } - else - { - MessageBox.Show("Failed to update with an automatic download. " + - "You'll likely have to update manually.", - "Error running automatic updates.", MessageBoxButtons.OK, - MessageBoxIcon.Error); - return; - } - } + FileName = url, + UseShellExecute = true + }; + Process.Start(website); + }; } else { - Console.WriteLine($"Up-to-date."); + Console.WriteLine("Up-to-date."); + UpdaterPopup.Dispose(); } } catch (Exception ex) diff --git a/Base/Forms/SetZoomForm.cs b/Base/Forms/SetZoomForm.cs index 231929c..939a7fc 100644 --- a/Base/Forms/SetZoomForm.cs +++ b/Base/Forms/SetZoomForm.cs @@ -98,6 +98,17 @@ public partial class SetZoomForm : Form { Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph; + if (minX > max.x) + { + MaxBoxX.Text = MinBoxX.Text; + MaxBoxX_Finish(sender, e); + minX = max.x; + + // Redefine bounds. + min = refForm.MinVisibleGraph; + max = refForm.MaxVisibleGraph; + } + double newCenterX = (minX + max.x) / 2, zoomFactorX = (max.x - minX) / (max.x - min.x); @@ -113,6 +124,17 @@ public partial class SetZoomForm : Form { Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph; + if (maxX < min.x) + { + MinBoxX.Text = MaxBoxX.Text; + MinBoxX_Finish(sender, e); + maxX = min.x; + + // Redefine bounds. + min = refForm.MinVisibleGraph; + max = refForm.MaxVisibleGraph; + } + double newCenterX = (min.x + maxX) / 2, zoomFactorX = (maxX - min.x) / (max.x - min.x); @@ -128,6 +150,17 @@ public partial class SetZoomForm : Form { Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph; + if (minY > max.y) + { + MaxBoxY.Text = MinBoxY.Text; + MaxBoxY_Finish(sender, e); + minY = max.y; + + // Redefine bounds. + min = refForm.MinVisibleGraph; + max = refForm.MaxVisibleGraph; + } + double newCenterY = -(minY + max.y) / 2, // Keeping it positive flips it for some reason ??? zoomFactorY = (max.y - minY) / (max.y - min.y); @@ -143,6 +176,17 @@ public partial class SetZoomForm : Form { Float2 min = refForm.MinVisibleGraph, max = refForm.MaxVisibleGraph; + if (maxY < min.y) + { + MinBoxY.Text = MaxBoxY.Text; + MinBoxY_Finish(sender, e); + maxY = min.y; + + // Redefine bounds. + min = refForm.MinVisibleGraph; + max = refForm.MaxVisibleGraph; + } + double newCenterY = -(min.y + maxY) / 2, // Keeping it positive flips it for some reason ??? zoomFactorY = (maxY - min.y) / (max.y - min.y); diff --git a/Base/Helpers/UpdaterHelper.cs b/Base/Helpers/UpdaterHelper.cs deleted file mode 100644 index b8c6fda..0000000 --- a/Base/Helpers/UpdaterHelper.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace Graphing.Helpers; - -internal static class UpdaterHelper -{ - public static async Task UsingNugetPackage(string? project = null) - { - project ??= GetProjectPath(); - - Process packageListProc = new(); - packageListProc.StartInfo.FileName = "dotnet"; - packageListProc.StartInfo.Arguments = "list package"; - packageListProc.StartInfo.WorkingDirectory = Path.GetDirectoryName(project); - - packageListProc.StartInfo.UseShellExecute = false; - packageListProc.StartInfo.RedirectStandardOutput = true; - - StringBuilder contentBuilder = new(); - packageListProc.OutputDataReceived += (o, e) => contentBuilder.AppendLine(e.Data); - - packageListProc.Start(); - packageListProc.BeginOutputReadLine(); - await packageListProc.WaitForExitAsync(); - - string content = contentBuilder.ToString(); - return content.Contains($"ThatOneNerd.Graphing"); - } - public static async Task UpdateProjectByNuget(string version, string? project = null) - { - project ??= GetProjectPath(); - - Process updateProc = new(); - updateProc.StartInfo.FileName = "dotnet"; - updateProc.StartInfo.Arguments = $"add package ThatOneNerd.Graphing --version {version}"; - updateProc.StartInfo.WorkingDirectory = Path.GetDirectoryName(project); - - updateProc.StartInfo.UseShellExecute = false; - updateProc.StartInfo.RedirectStandardError = true; - - StringBuilder errorBuilder = new(); - updateProc.ErrorDataReceived += (o, e) => errorBuilder.AppendLine(e.Data); - - updateProc.Start(); - updateProc.BeginErrorReadLine(); - await updateProc.WaitForExitAsync(); - - // Could be shrunk but it makes it less clear. If there's any data written to - // the error stream, it did not succeed. - if (errorBuilder.Length > 0) return false; // Error. - else return true; // Success. - } - public static async Task UpdateProjectByGitHub(string version, string? project = null) - { - // TODO - return false; - } - - public static string? GetProjectPath() - { - Assembly? entryAsm = Assembly.GetEntryAssembly(); - if (entryAsm is null) return null; - - string? directory = Path.GetDirectoryName(entryAsm.Location); - while (directory is not null) - { - string[] files = Directory.GetFiles(directory); - string? project = files.FirstOrDefault(x => x.EndsWith(".csproj") || - x.EndsWith(".sln")); - if (project is not null) return project; - - directory = Path.GetDirectoryName(directory); - } - - return null; - } -} -- 2.49.0.windows.1 From ea9d2cd3b633a7b28e44c940faff4abe01a427c7 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Wed, 24 Apr 2024 08:42:32 -0400 Subject: [PATCH 17/21] Slope field works with decimals now. --- Base/Forms/GraphForm.Designer.cs | 16 ++++++++-------- Base/Graphables/SlopeField.cs | 16 +++++++++++----- Testing/Program.cs | 7 +++++-- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Base/Forms/GraphForm.Designer.cs b/Base/Forms/GraphForm.Designer.cs index 54a8122..edfcd4e 100644 --- a/Base/Forms/GraphForm.Designer.cs +++ b/Base/Forms/GraphForm.Designer.cs @@ -72,7 +72,7 @@ namespace Graphing.Forms GraphMenu.Items.AddRange(new ToolStripItem[] { MenuViewport, MenuElements, MenuOperations, MenuConvert, MenuMisc }); GraphMenu.Location = new Point(0, 0); GraphMenu.Name = "GraphMenu"; - GraphMenu.Size = new Size(1449, 40); + GraphMenu.Size = new Size(1449, 42); GraphMenu.TabIndex = 1; GraphMenu.Text = "menuStrip1"; // @@ -80,7 +80,7 @@ namespace Graphing.Forms // MenuViewport.DropDownItems.AddRange(new ToolStripItem[] { ButtonViewportSetZoom, ButtonViewportSetCenter, ButtonViewportReset, ButtonViewportResetWindow }); MenuViewport.Name = "MenuViewport"; - MenuViewport.Size = new Size(129, 36); + MenuViewport.Size = new Size(129, 38); MenuViewport.Text = "Viewport"; // // ButtonViewportSetZoom @@ -115,7 +115,7 @@ namespace Graphing.Forms // MenuElements.DropDownItems.AddRange(new ToolStripItem[] { MenuElementsColors, MenuElementsRemove }); MenuElements.Name = "MenuElements"; - MenuElements.Size = new Size(131, 36); + MenuElements.Size = new Size(131, 38); MenuElements.Text = "Elements"; // // MenuElementsColors @@ -134,7 +134,7 @@ namespace Graphing.Forms // MenuOperations.DropDownItems.AddRange(new ToolStripItem[] { MenuOperationsDerivative, MenuOperationsIntegral, MenuOperationsTranslate }); MenuOperations.Name = "MenuOperations"; - MenuOperations.Size = new Size(151, 36); + MenuOperations.Size = new Size(151, 38); MenuOperations.Text = "Operations"; // // MenuOperationsDerivative @@ -159,26 +159,26 @@ namespace Graphing.Forms // MenuConvert.DropDownItems.AddRange(new ToolStripItem[] { MenuConvertEquation, MenuConvertSlopeField }); MenuConvert.Name = "MenuConvert"; - MenuConvert.Size = new Size(118, 36); + MenuConvert.Size = new Size(118, 38); MenuConvert.Text = "Convert"; // // MenuConvertEquation // MenuConvertEquation.Name = "MenuConvertEquation"; - MenuConvertEquation.Size = new Size(297, 44); + MenuConvertEquation.Size = new Size(359, 44); MenuConvertEquation.Text = "To Equation"; // // MenuConvertSlopeField // MenuConvertSlopeField.Name = "MenuConvertSlopeField"; - MenuConvertSlopeField.Size = new Size(297, 44); + MenuConvertSlopeField.Size = new Size(359, 44); MenuConvertSlopeField.Text = "To Slope Field"; // // MenuMisc // MenuMisc.DropDownItems.AddRange(new ToolStripItem[] { MenuMiscCaches, MiscMenuPreload }); MenuMisc.Name = "MenuMisc"; - MenuMisc.Size = new Size(83, 36); + MenuMisc.Size = new Size(83, 38); MenuMisc.Text = "Misc"; // // MenuMiscCaches diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs index 701f506..1ce56b9 100644 --- a/Base/Graphables/SlopeField.cs +++ b/Base/Graphables/SlopeField.cs @@ -11,11 +11,11 @@ public class SlopeField : Graphable private static int slopeFieldNum; protected readonly SlopeFieldsDelegate equ; - protected readonly int detail; + protected readonly double detail; protected readonly List<(Float2, GraphLine)> cache; - public SlopeField(int detail, SlopeFieldsDelegate equ) + public SlopeField(double detail, SlopeFieldsDelegate equ) { slopeFieldNum++; Name = $"Slope Field {slopeFieldNum}"; @@ -27,12 +27,18 @@ public class SlopeField : Graphable public override IEnumerable GetItemsToRender(in GraphForm graph) { - double epsilon = 1 / (detail * 2.0); + double step = 1 / detail; + double epsilon = step * 0.5; List lines = []; - for (double x = Math.Ceiling(graph.MinVisibleGraph.x - 1); x < graph.MaxVisibleGraph.x + 1; x += 1.0 / detail) + double minX = Math.Round((graph.MinVisibleGraph.x - 1) / step) * step, + maxX = Math.Round((graph.MaxVisibleGraph.x + 1) / step) * step, + minY = Math.Round((graph.MinVisibleGraph.y - 1) / step) * step, + maxY = Math.Round((graph.MaxVisibleGraph.y + 1) / step) * step; + + for (double x = minX; x < maxX; x += step) { - for (double y = Math.Ceiling(graph.MinVisibleGraph.y - 1); y < graph.MaxVisibleGraph.y + 1; y += 1.0 / detail) + for (double y = minY; y < maxY; y += step) { lines.Add(GetFromCache(epsilon, x, y)); } diff --git a/Testing/Program.cs b/Testing/Program.cs index 35082d7..87a07fa 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -16,12 +16,15 @@ internal static class Program GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - Equation equA = new(Math.Sin), + /*Equation equA = new(Math.Sin), equB = new(Math.Cos); EquationDifference diff = new(2, equA, equB); ParametricEquation equC = new(0, 20, t => 0.0375 * t * Math.Cos(t), t => 0.0625 * t * Math.Sin(t) + 3); TangentLine tanA = new(2, 2, equA); - graph.Graph(equA, equB, diff, equC, equB.ToColumnTable(-3, 3, 2), tanA); + graph.Graph(equA, equB, diff, equC, equB.ToColumnTable(-3, 3, 2), tanA);*/ + + SlopeField sf1 = new(1.5, (x, y) => Math.Cos(x) + Math.Sin(y)); + graph.Graph(sf1); Application.Run(graph); } -- 2.49.0.windows.1 From eea0bba3585b2545837f25057372ffdf77507251 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Mon, 29 Apr 2024 07:34:59 -0400 Subject: [PATCH 18/21] Slope field detail changer works. --- Base/Base.csproj.user | 3 + Base/Forms/GraphForm.Designer.cs | 83 ++++++----- Base/Forms/GraphForm.cs | 41 ++++++ Base/Forms/SlopeFieldDetailForm.Designer.cs | 147 ++++++++++++++++++++ Base/Forms/SlopeFieldDetailForm.cs | 130 +++++++++++++++++ Base/Forms/SlopeFieldDetailForm.resx | 120 ++++++++++++++++ Base/Graphables/SlopeField.cs | 43 ++++-- Testing/Program.cs | 7 +- 8 files changed, 518 insertions(+), 56 deletions(-) create mode 100644 Base/Forms/SlopeFieldDetailForm.Designer.cs create mode 100644 Base/Forms/SlopeFieldDetailForm.cs create mode 100644 Base/Forms/SlopeFieldDetailForm.resx diff --git a/Base/Base.csproj.user b/Base/Base.csproj.user index b8c9d44..8d9b1c8 100644 --- a/Base/Base.csproj.user +++ b/Base/Base.csproj.user @@ -16,6 +16,9 @@ Form + + Form + Form diff --git a/Base/Forms/GraphForm.Designer.cs b/Base/Forms/GraphForm.Designer.cs index 2de0a19..ec64887 100644 --- a/Base/Forms/GraphForm.Designer.cs +++ b/Base/Forms/GraphForm.Designer.cs @@ -55,6 +55,7 @@ namespace Graphing.Forms UpdaterPopupDownloadButton = new Button(); UpdaterPopupCloseButton = new Button(); UpdaterPopupMessage = new Label(); + MenuElementsDetail = new ToolStripMenuItem(); GraphMenu.SuspendLayout(); UpdaterPopup.SuspendLayout(); SuspendLayout(); @@ -63,10 +64,10 @@ namespace Graphing.Forms // ResetViewportButton.Anchor = AnchorStyles.Top | AnchorStyles.Right; ResetViewportButton.Font = new Font("Segoe UI Emoji", 12F, FontStyle.Regular, GraphicsUnit.Point, 0); - ResetViewportButton.Location = new Point(739, 20); - ResetViewportButton.Margin = new Padding(2, 1, 2, 1); + ResetViewportButton.Location = new Point(1372, 43); + ResetViewportButton.Margin = new Padding(4, 2, 4, 2); ResetViewportButton.Name = "ResetViewportButton"; - ResetViewportButton.Size = new Size(34, 30); + ResetViewportButton.Size = new Size(63, 64); ResetViewportButton.TabIndex = 0; ResetViewportButton.Text = "🏠"; ResetViewportButton.UseVisualStyleBackColor = true; @@ -78,8 +79,7 @@ namespace Graphing.Forms GraphMenu.Items.AddRange(new ToolStripItem[] { MenuViewport, MenuElements, MenuOperations, MenuConvert, MenuMisc }); GraphMenu.Location = new Point(0, 0); GraphMenu.Name = "GraphMenu"; - GraphMenu.Padding = new Padding(3, 1, 0, 1); - GraphMenu.Size = new Size(780, 24); + GraphMenu.Size = new Size(1449, 42); GraphMenu.TabIndex = 1; GraphMenu.Text = "menuStrip1"; // @@ -87,118 +87,118 @@ namespace Graphing.Forms // MenuViewport.DropDownItems.AddRange(new ToolStripItem[] { ButtonViewportSetZoom, ButtonViewportSetCenter, ButtonViewportReset, ButtonViewportResetWindow }); MenuViewport.Name = "MenuViewport"; - MenuViewport.Size = new Size(66, 22); + MenuViewport.Size = new Size(129, 38); MenuViewport.Text = "Viewport"; // // ButtonViewportSetZoom // ButtonViewportSetZoom.Name = "ButtonViewportSetZoom"; - ButtonViewportSetZoom.Size = new Size(174, 22); + ButtonViewportSetZoom.Size = new Size(359, 44); ButtonViewportSetZoom.Text = "Set Zoom"; ButtonViewportSetZoom.Click += ButtonViewportSetZoom_Click; // // ButtonViewportSetCenter // ButtonViewportSetCenter.Name = "ButtonViewportSetCenter"; - ButtonViewportSetCenter.Size = new Size(174, 22); + ButtonViewportSetCenter.Size = new Size(359, 44); ButtonViewportSetCenter.Text = "Set Center Position"; ButtonViewportSetCenter.Click += ButtonViewportSetCenter_Click; // // ButtonViewportReset // ButtonViewportReset.Name = "ButtonViewportReset"; - ButtonViewportReset.Size = new Size(174, 22); + ButtonViewportReset.Size = new Size(359, 44); ButtonViewportReset.Text = "Reset Viewport"; ButtonViewportReset.Click += ButtonViewportReset_Click; // // ButtonViewportResetWindow // ButtonViewportResetWindow.Name = "ButtonViewportResetWindow"; - ButtonViewportResetWindow.Size = new Size(174, 22); + ButtonViewportResetWindow.Size = new Size(359, 44); ButtonViewportResetWindow.Text = "Reset Window Size"; ButtonViewportResetWindow.Click += ButtonViewportResetWindow_Click; // // MenuElements // - MenuElements.DropDownItems.AddRange(new ToolStripItem[] { MenuElementsColors, MenuElementsRemove }); + MenuElements.DropDownItems.AddRange(new ToolStripItem[] { MenuElementsColors, MenuElementsDetail, MenuElementsRemove }); MenuElements.Name = "MenuElements"; - MenuElements.Size = new Size(67, 22); + MenuElements.Size = new Size(131, 38); MenuElements.Text = "Elements"; // // MenuElementsColors // MenuElementsColors.Name = "MenuElementsColors"; - MenuElementsColors.Size = new Size(117, 22); + MenuElementsColors.Size = new Size(359, 44); MenuElementsColors.Text = "Colors"; // // MenuElementsRemove // MenuElementsRemove.Name = "MenuElementsRemove"; - MenuElementsRemove.Size = new Size(117, 22); + MenuElementsRemove.Size = new Size(359, 44); MenuElementsRemove.Text = "Remove"; // // MenuOperations // MenuOperations.DropDownItems.AddRange(new ToolStripItem[] { MenuOperationsDerivative, MenuOperationsIntegral, MenuOperationsTranslate }); MenuOperations.Name = "MenuOperations"; - MenuOperations.Size = new Size(77, 22); + MenuOperations.Size = new Size(151, 38); MenuOperations.Text = "Operations"; // // MenuOperationsDerivative // MenuOperationsDerivative.Name = "MenuOperationsDerivative"; - MenuOperationsDerivative.Size = new Size(179, 22); + MenuOperationsDerivative.Size = new Size(360, 44); MenuOperationsDerivative.Text = "Compute Derivative"; // // MenuOperationsIntegral // MenuOperationsIntegral.Name = "MenuOperationsIntegral"; - MenuOperationsIntegral.Size = new Size(179, 22); + MenuOperationsIntegral.Size = new Size(360, 44); MenuOperationsIntegral.Text = "Compute Integral"; // // MenuOperationsTranslate // MenuOperationsTranslate.Name = "MenuOperationsTranslate"; - MenuOperationsTranslate.Size = new Size(179, 22); + MenuOperationsTranslate.Size = new Size(360, 44); MenuOperationsTranslate.Text = "Translate"; // // MenuConvert // MenuConvert.DropDownItems.AddRange(new ToolStripItem[] { MenuConvertEquation, MenuConvertSlopeField }); MenuConvert.Name = "MenuConvert"; - MenuConvert.Size = new Size(61, 22); + MenuConvert.Size = new Size(118, 38); MenuConvert.Text = "Convert"; // // MenuConvertEquation // MenuConvertEquation.Name = "MenuConvertEquation"; - MenuConvertEquation.Size = new Size(146, 22); + MenuConvertEquation.Size = new Size(297, 44); MenuConvertEquation.Text = "To Equation"; // // MenuConvertSlopeField // MenuConvertSlopeField.Name = "MenuConvertSlopeField"; - MenuConvertSlopeField.Size = new Size(146, 22); + MenuConvertSlopeField.Size = new Size(297, 44); MenuConvertSlopeField.Text = "To Slope Field"; // // MenuMisc // MenuMisc.DropDownItems.AddRange(new ToolStripItem[] { MenuMiscCaches, MiscMenuPreload }); MenuMisc.Name = "MenuMisc"; - MenuMisc.Size = new Size(44, 22); + MenuMisc.Size = new Size(83, 38); MenuMisc.Text = "Misc"; // // MenuMiscCaches // MenuMiscCaches.Name = "MenuMiscCaches"; - MenuMiscCaches.Size = new Size(150, 22); + MenuMiscCaches.Size = new Size(299, 44); MenuMiscCaches.Text = "View Caches"; MenuMiscCaches.Click += MenuMiscCaches_Click; // // MiscMenuPreload // MiscMenuPreload.Name = "MiscMenuPreload"; - MiscMenuPreload.Size = new Size(150, 22); + MiscMenuPreload.Size = new Size(299, 44); MiscMenuPreload.Text = "Preload Cache"; MiscMenuPreload.Click += MiscMenuPreload_Click; // @@ -210,18 +210,20 @@ namespace Graphing.Forms UpdaterPopup.Controls.Add(UpdaterPopupDownloadButton); UpdaterPopup.Controls.Add(UpdaterPopupCloseButton); UpdaterPopup.Controls.Add(UpdaterPopupMessage); - UpdaterPopup.Location = new Point(520, 371); + UpdaterPopup.Location = new Point(966, 791); + UpdaterPopup.Margin = new Padding(6, 6, 6, 6); UpdaterPopup.Name = "UpdaterPopup"; - UpdaterPopup.Size = new Size(261, 55); + UpdaterPopup.Size = new Size(483, 115); UpdaterPopup.TabIndex = 2; UpdaterPopup.Visible = false; // // UpdaterPopupDownloadButton // UpdaterPopupDownloadButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; - UpdaterPopupDownloadButton.Location = new Point(181, 27); + UpdaterPopupDownloadButton.Location = new Point(336, 58); + UpdaterPopupDownloadButton.Margin = new Padding(6, 6, 6, 6); UpdaterPopupDownloadButton.Name = "UpdaterPopupDownloadButton"; - UpdaterPopupDownloadButton.Size = new Size(75, 23); + UpdaterPopupDownloadButton.Size = new Size(139, 49); UpdaterPopupDownloadButton.TabIndex = 2; UpdaterPopupDownloadButton.Text = "Visit"; UpdaterPopupDownloadButton.UseVisualStyleBackColor = true; @@ -229,10 +231,10 @@ namespace Graphing.Forms // UpdaterPopupCloseButton // UpdaterPopupCloseButton.Anchor = AnchorStyles.Top | AnchorStyles.Right; - UpdaterPopupCloseButton.Location = new Point(234, 1); - UpdaterPopupCloseButton.Margin = new Padding(1); + UpdaterPopupCloseButton.Location = new Point(435, 2); + UpdaterPopupCloseButton.Margin = new Padding(2, 2, 2, 2); UpdaterPopupCloseButton.Name = "UpdaterPopupCloseButton"; - UpdaterPopupCloseButton.Size = new Size(24, 24); + UpdaterPopupCloseButton.Size = new Size(45, 51); UpdaterPopupCloseButton.TabIndex = 1; UpdaterPopupCloseButton.Text = "X"; UpdaterPopupCloseButton.UseVisualStyleBackColor = true; @@ -242,23 +244,29 @@ namespace Graphing.Forms // UpdaterPopupMessage.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left; UpdaterPopupMessage.Font = new Font("Segoe UI", 9.75F, FontStyle.Bold, GraphicsUnit.Point, 0); - UpdaterPopupMessage.Location = new Point(3, 3); - UpdaterPopupMessage.Margin = new Padding(3); + UpdaterPopupMessage.Location = new Point(6, 6); + UpdaterPopupMessage.Margin = new Padding(6, 6, 6, 6); UpdaterPopupMessage.Name = "UpdaterPopupMessage"; - UpdaterPopupMessage.Size = new Size(228, 47); + UpdaterPopupMessage.Size = new Size(423, 100); UpdaterPopupMessage.TabIndex = 0; UpdaterPopupMessage.Text = "A update is available!\r\nA.B.C → E.F.G"; // + // MenuElementsDetail + // + MenuElementsDetail.Name = "MenuElementsDetail"; + MenuElementsDetail.Size = new Size(359, 44); + MenuElementsDetail.Text = "Detail"; + // // GraphForm // - AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleDimensions = new SizeF(13F, 32F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(780, 425); + ClientSize = new Size(1449, 907); Controls.Add(UpdaterPopup); Controls.Add(ResetViewportButton); Controls.Add(GraphMenu); MainMenuStrip = GraphMenu; - Margin = new Padding(2, 1, 2, 1); + Margin = new Padding(4, 2, 4, 2); Name = "GraphForm"; Text = "GraphFormBase"; GraphMenu.ResumeLayout(false); @@ -294,5 +302,6 @@ namespace Graphing.Forms private Label UpdaterPopupMessage; private Button UpdaterPopupCloseButton; private Button UpdaterPopupDownloadButton; + private ToolStripMenuItem MenuElementsDetail; } } \ No newline at end of file diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index df116d7..4091d0f 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -1,4 +1,5 @@ using Graphing.Abstract; +using Graphing.Graphables; using System; using System.Collections.Generic; using System.Diagnostics; @@ -444,9 +445,38 @@ public partial class GraphForm : Form RegenerateMenuItems(); } + private readonly Dictionary sfDetailForms = []; + private void ChangeSlopeFieldDetail(SlopeField sf) + { + if (sfDetailForms.TryGetValue(sf, out SlopeFieldDetailForm? preexistingForm)) + { + preexistingForm.Focus(); + return; + } + + SlopeFieldDetailForm detailForm = new(this, sf) + { + StartPosition = FormStartPosition.Manual + }; + sfDetailForms.Add(sf, detailForm); + + detailForm.Location = new Point(Location.X + ClientRectangle.Width + 10, + Location.Y + (ClientRectangle.Height - detailForm.ClientRectangle.Height) / 2); + + if (detailForm.Location.X + detailForm.Width > Screen.FromControl(this).WorkingArea.Width) + { + detailForm.StartPosition = FormStartPosition.WindowsDefaultLocation; + } + detailForm.TopMost = true; + detailForm.Show(); + + detailForm.FormClosed += (o, e) => sfDetailForms.Remove(sf); + } + private void RegenerateMenuItems() { MenuElementsColors.DropDownItems.Clear(); + MenuElementsDetail.DropDownItems.Clear(); MenuElementsRemove.DropDownItems.Clear(); MenuOperationsDerivative.DropDownItems.Clear(); MenuOperationsIntegral.DropDownItems.Clear(); @@ -474,6 +504,17 @@ public partial class GraphForm : Form removeItem.Click += (o, e) => Ungraph(able); MenuElementsRemove.DropDownItems.Add(removeItem); + if (able is SlopeField sf) + { + ToolStripMenuItem sfDetailItem = new() + { + ForeColor = able.Color, + Text = able.Name + }; + sfDetailItem.Click += (o, e) => ChangeSlopeFieldDetail(sf); + MenuElementsDetail.DropDownItems.Add(sfDetailItem); + } + if (able is IDerivable derivable) { ToolStripMenuItem derivativeItem = new() diff --git a/Base/Forms/SlopeFieldDetailForm.Designer.cs b/Base/Forms/SlopeFieldDetailForm.Designer.cs new file mode 100644 index 0000000..1d93ed2 --- /dev/null +++ b/Base/Forms/SlopeFieldDetailForm.Designer.cs @@ -0,0 +1,147 @@ +namespace Graphing.Forms +{ + partial class SlopeFieldDetailForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + Message = new System.Windows.Forms.Label(); + TrackSlopeDetail = new System.Windows.Forms.TrackBar(); + MinDetailBox = new System.Windows.Forms.TextBox(); + MaxDetailBox = new System.Windows.Forms.TextBox(); + CurrentDetailBox = new System.Windows.Forms.TextBox(); + IncrementButton = new System.Windows.Forms.Button(); + DecrementButton = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)TrackSlopeDetail).BeginInit(); + SuspendLayout(); + // + // Message + // + Message.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + Message.Location = new System.Drawing.Point(119, 25); + Message.Margin = new System.Windows.Forms.Padding(110); + Message.Name = "Message"; + Message.Size = new System.Drawing.Size(516, 109); + Message.TabIndex = 1; + Message.Text = "Change the Detail of %name%\r\nA higher value means more lines per unit."; + Message.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // TrackSlopeDetail + // + TrackSlopeDetail.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + TrackSlopeDetail.LargeChange = 250; + TrackSlopeDetail.Location = new System.Drawing.Point(59, 158); + TrackSlopeDetail.Margin = new System.Windows.Forms.Padding(50); + TrackSlopeDetail.Maximum = 1000; + TrackSlopeDetail.Name = "TrackSlopeDetail"; + TrackSlopeDetail.Size = new System.Drawing.Size(636, 90); + TrackSlopeDetail.SmallChange = 0; + TrackSlopeDetail.TabIndex = 0; + TrackSlopeDetail.TickFrequency = 0; + TrackSlopeDetail.TickStyle = System.Windows.Forms.TickStyle.Both; + TrackSlopeDetail.Scroll += TrackSlopeDetail_Scroll; + // + // MinDetailBox + // + MinDetailBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + MinDetailBox.Location = new System.Drawing.Point(12, 228); + MinDetailBox.Name = "MinDetailBox"; + MinDetailBox.Size = new System.Drawing.Size(100, 39); + MinDetailBox.TabIndex = 2; + // + // MaxDetailBox + // + MaxDetailBox.Anchor = System.Windows.Forms.AnchorStyles.Right; + MaxDetailBox.Location = new System.Drawing.Point(642, 228); + MaxDetailBox.Name = "MaxDetailBox"; + MaxDetailBox.Size = new System.Drawing.Size(100, 39); + MaxDetailBox.TabIndex = 3; + MaxDetailBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // + // CurrentDetailBox + // + CurrentDetailBox.Anchor = System.Windows.Forms.AnchorStyles.None; + CurrentDetailBox.Location = new System.Drawing.Point(330, 228); + CurrentDetailBox.Name = "CurrentDetailBox"; + CurrentDetailBox.Size = new System.Drawing.Size(100, 39); + CurrentDetailBox.TabIndex = 4; + CurrentDetailBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + // + // IncrementButton + // + IncrementButton.Anchor = System.Windows.Forms.AnchorStyles.None; + IncrementButton.Font = new System.Drawing.Font("Segoe UI", 7.875F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); + IncrementButton.Location = new System.Drawing.Point(436, 228); + IncrementButton.Name = "IncrementButton"; + IncrementButton.Size = new System.Drawing.Size(40, 40); + IncrementButton.TabIndex = 5; + IncrementButton.Text = "+"; + IncrementButton.UseVisualStyleBackColor = true; + IncrementButton.Click += IncrementButton_Click; + // + // DecrementButton + // + DecrementButton.Anchor = System.Windows.Forms.AnchorStyles.None; + DecrementButton.Font = new System.Drawing.Font("Segoe UI", 7.875F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); + DecrementButton.Location = new System.Drawing.Point(284, 228); + DecrementButton.Name = "DecrementButton"; + DecrementButton.Size = new System.Drawing.Size(40, 40); + DecrementButton.TabIndex = 6; + DecrementButton.Text = "-"; + DecrementButton.UseVisualStyleBackColor = true; + DecrementButton.Click += DecrementButton_Click; + // + // SlopeFieldDetailForm + // + AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(754, 282); + Controls.Add(DecrementButton); + Controls.Add(IncrementButton); + Controls.Add(CurrentDetailBox); + Controls.Add(MaxDetailBox); + Controls.Add(MinDetailBox); + Controls.Add(Message); + Controls.Add(TrackSlopeDetail); + FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + Name = "SlopeFieldDetailForm"; + Text = "Change Slope Field Detail"; + ((System.ComponentModel.ISupportInitialize)TrackSlopeDetail).EndInit(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private System.Windows.Forms.Label Message; + private System.Windows.Forms.TrackBar TrackSlopeDetail; + private System.Windows.Forms.TextBox MinDetailBox; + private System.Windows.Forms.TextBox MaxDetailBox; + private System.Windows.Forms.TextBox CurrentDetailBox; + private System.Windows.Forms.Button IncrementButton; + private System.Windows.Forms.Button DecrementButton; + } +} \ No newline at end of file diff --git a/Base/Forms/SlopeFieldDetailForm.cs b/Base/Forms/SlopeFieldDetailForm.cs new file mode 100644 index 0000000..20575c7 --- /dev/null +++ b/Base/Forms/SlopeFieldDetailForm.cs @@ -0,0 +1,130 @@ +using Graphing.Graphables; +using System; +using System.Windows.Forms; + +namespace Graphing.Forms; + +public partial class SlopeFieldDetailForm : Form +{ + private readonly GraphForm refForm; + private readonly SlopeField slopeField; + + private double minDetail, maxDetail; + + public SlopeFieldDetailForm(GraphForm form, SlopeField sf) + { + InitializeComponent(); + + refForm = form; + slopeField = sf; + + refForm.Paint += (o, e) => RedeclareValues(); + RedeclareValues(); + + TrackSlopeDetail.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Right) IncrementButton_Click(o, e); + else if (e.KeyCode == Keys.Left) DecrementButton_Click(o, e); + }; + + MinDetailBox.Leave += MinDetailBox_Finish; + MinDetailBox.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) MinDetailBox_Finish(o, e); + }; + MaxDetailBox.Leave += MaxDetailBox_Finish; + MaxDetailBox.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) MaxDetailBox_Finish(o, e); + }; + CurrentDetailBox.Leave += CurrentDetailBox_Finish; + CurrentDetailBox.KeyDown += (o, e) => + { + if (e.KeyCode == Keys.Enter) CurrentDetailBox_Finish(o, e); + }; + + minDetail = sf.Detail / 2; + maxDetail = sf.Detail * 2; + + Message.Text = Message.Text.Replace("%name%", sf.Name); + } + + // Exponential interpolations are better than simple lerps here since + // we're scaling a multiple rather than an additive. + private double Interp(double t) + { + // This is weird. I don't like the +1s and -1s, I don't think I wrote this right. + // But it seems to get the job done. + return minDetail + Math.Pow(2, t * Math.Log2(maxDetail - minDetail + 1)) - 1; + } + private double InverseInterp(double c) + { + return Math.Log2(c - minDetail + 1) / Math.Log2(maxDetail - minDetail + 1); + } + + private void RedeclareValues() + { + double detail = slopeField.Detail; + if (detail < minDetail) minDetail = detail; + else if (detail > maxDetail) maxDetail = detail; + + double t = InverseInterp(detail); + TrackSlopeDetail.Value = (int)(TrackSlopeDetail.Minimum + t * (TrackSlopeDetail.Maximum - TrackSlopeDetail.Minimum)); + + MinDetailBox.Text = $"{minDetail:0.00}"; + MaxDetailBox.Text = $"{maxDetail:0.00}"; + CurrentDetailBox.Text = $"{detail:0.00}"; + } + + private void TrackSlopeDetail_Scroll(object? sender, EventArgs e) + { + double t = (double)(TrackSlopeDetail.Value - TrackSlopeDetail.Minimum) / (TrackSlopeDetail.Maximum - TrackSlopeDetail.Minimum); + double newDetail = Interp(t); + + slopeField.Detail = newDetail; + refForm.Invalidate(false); + } + private void MinDetailBox_Finish(object? sender, EventArgs e) + { + if (double.TryParse(MinDetailBox.Text, out double newMinDetail)) + { + minDetail = newMinDetail; + if (minDetail > slopeField.Detail) slopeField.Detail = newMinDetail; + } + refForm.Invalidate(false); + } + private void MaxDetailBox_Finish(object? sender, EventArgs e) + { + if (double.TryParse(MaxDetailBox.Text, out double newMaxDetail)) + { + maxDetail = newMaxDetail; + if (maxDetail < slopeField.Detail) slopeField.Detail = newMaxDetail; + } + refForm.Invalidate(false); + } + private void CurrentDetailBox_Finish(object? sender, EventArgs e) + { + if (double.TryParse(CurrentDetailBox.Text, out double newDetail)) + { + if (newDetail < minDetail) minDetail = newDetail; + else if (newDetail > maxDetail) maxDetail = newDetail; + slopeField.Detail = newDetail; + } + refForm.Invalidate(false); + } + + private void IncrementButton_Click(object? sender, EventArgs e) + { + double newDetail = slopeField.Detail * 1.0625f; + if (newDetail > maxDetail) maxDetail = newDetail; + slopeField.Detail = newDetail; + refForm.Invalidate(false); + } + private void DecrementButton_Click(object? sender, EventArgs e) + { + double newDetail = slopeField.Detail / 1.0625f; + if (newDetail < minDetail) minDetail = newDetail; + slopeField.Detail = newDetail; + refForm.Invalidate(false); + } +} diff --git a/Base/Forms/SlopeFieldDetailForm.resx b/Base/Forms/SlopeFieldDetailForm.resx new file mode 100644 index 0000000..af32865 --- /dev/null +++ b/Base/Forms/SlopeFieldDetailForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Base/Graphables/SlopeField.cs b/Base/Graphables/SlopeField.cs index 1ce56b9..44b877e 100644 --- a/Base/Graphables/SlopeField.cs +++ b/Base/Graphables/SlopeField.cs @@ -10,9 +10,24 @@ public class SlopeField : Graphable { private static int slopeFieldNum; - protected readonly SlopeFieldsDelegate equ; - protected readonly double detail; + public double Detail + { + get => _detail; + set + { + if (Math.Abs(value - Detail) >= 1e-4) + { + // When changing detail, we need to regenerate all + // the lines. Inefficient, I know. Might be optimized + // in a future update. + EraseCache(); + } + _detail = value; + } + } + private double _detail; + protected readonly SlopeFieldsDelegate equ; protected readonly List<(Float2, GraphLine)> cache; public SlopeField(double detail, SlopeFieldsDelegate equ) @@ -21,13 +36,13 @@ public class SlopeField : Graphable Name = $"Slope Field {slopeFieldNum}"; this.equ = equ; - this.detail = detail; + _detail = detail; cache = []; } public override IEnumerable GetItemsToRender(in GraphForm graph) { - double step = 1 / detail; + double step = 1 / _detail; double epsilon = step * 0.5; List lines = []; @@ -49,7 +64,7 @@ public class SlopeField : Graphable protected GraphLine MakeSlopeLine(Float2 position, double slope) { - double size = detail; + double size = _detail; double dirX = size, dirY = slope * size; double magnitude = Math.Sqrt(dirX * dirX + dirY * dirY); @@ -79,17 +94,17 @@ public class SlopeField : Graphable return result; } - public override Graphable ShallowCopy() => new SlopeField(detail, equ); + public override Graphable ShallowCopy() => new SlopeField(_detail, equ); public override void EraseCache() => cache.Clear(); public override long GetCacheBytes() => cache.Count * 48; public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor) { - Float2 nearestPos = new(Math.Round(graphMousePos.x * detail) / detail, - Math.Round(graphMousePos.y * detail) / detail); + Float2 nearestPos = new(Math.Round(graphMousePos.x * _detail) / _detail, + Math.Round(graphMousePos.y * _detail) / _detail); - double epsilon = 1 / (detail * 2.0); + double epsilon = 1 / (_detail * 2.0); GraphLine line = GetFromCache(epsilon, nearestPos.x, nearestPos.y); double slope = (line.b.y - line.a.y) / (line.b.x - line.a.x); @@ -110,10 +125,10 @@ public class SlopeField : Graphable } public override IEnumerable GetSelectionItemsToRender(in GraphForm graph, Float2 graphMousePos) { - Float2 nearestPos = new(Math.Round(graphMousePos.x * detail) / detail, - Math.Round(graphMousePos.y * detail) / detail); + Float2 nearestPos = new(Math.Round(graphMousePos.x * _detail) / _detail, + Math.Round(graphMousePos.y * _detail) / _detail); - double epsilon = 1 / (detail * 2.0); + double epsilon = 1 / (_detail * 2.0); GraphLine line = GetFromCache(epsilon, nearestPos.x, nearestPos.y); double slope = (line.b.y - line.a.y) / (line.b.x - line.a.x); @@ -130,9 +145,9 @@ public class SlopeField : Graphable public override void Preload(Float2 xRange, Float2 yRange, double step) { - for (double x = Math.Ceiling(xRange.x - 1); x < xRange.y + 1; x += 1.0 / detail) + for (double x = Math.Ceiling(xRange.x - 1); x < xRange.y + 1; x += 1.0 / _detail) { - for (double y = Math.Ceiling(yRange.x - 1); y < yRange.y + 1; y += 1.0 / detail) + for (double y = Math.Ceiling(yRange.x - 1); y < yRange.y + 1; y += 1.0 / _detail) { GetFromCache(step, x, y); } diff --git a/Testing/Program.cs b/Testing/Program.cs index 87a07fa..35082d7 100644 --- a/Testing/Program.cs +++ b/Testing/Program.cs @@ -16,15 +16,12 @@ internal static class Program GraphForm graph = new("One Of The Graphing Calculators Of All Time"); - /*Equation equA = new(Math.Sin), + Equation equA = new(Math.Sin), equB = new(Math.Cos); EquationDifference diff = new(2, equA, equB); ParametricEquation equC = new(0, 20, t => 0.0375 * t * Math.Cos(t), t => 0.0625 * t * Math.Sin(t) + 3); TangentLine tanA = new(2, 2, equA); - graph.Graph(equA, equB, diff, equC, equB.ToColumnTable(-3, 3, 2), tanA);*/ - - SlopeField sf1 = new(1.5, (x, y) => Math.Cos(x) + Math.Sin(y)); - graph.Graph(sf1); + graph.Graph(equA, equB, diff, equC, equB.ToColumnTable(-3, 3, 2), tanA); Application.Run(graph); } -- 2.49.0.windows.1 From 861c7c5a0365e7fdf998e059a6229d1b513dc0b5 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Wed, 1 May 2024 12:30:48 -0400 Subject: [PATCH 19/21] Zooming now focuses on the mouse cursor. --- Base/Forms/GraphForm.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Base/Forms/GraphForm.cs b/Base/Forms/GraphForm.cs index 4091d0f..b4630ab 100644 --- a/Base/Forms/GraphForm.cs +++ b/Base/Forms/GraphForm.cs @@ -414,11 +414,20 @@ public partial class GraphForm : Form { if (ViewportLocked) return; + Point clientMousePos = PointToClient(Cursor.Position); + Int2 mousePos = new(clientMousePos.X, clientMousePos.Y); + Float2 mouseOver = ScreenSpaceToGraphSpace(mousePos); + Float2 newZoom = ZoomLevel; newZoom.x *= 1 - e.Delta * 0.00075; // Zoom factor. newZoom.y *= 1 - e.Delta * 0.00075; ZoomLevel = newZoom; + // Keep the mouse as the zoom hotspot. + Float2 newOver = ScreenSpaceToGraphSpace(mousePos); + Float2 delta = new(newOver.x - mouseOver.x, newOver.y - mouseOver.y); + ScreenCenter = new(ScreenCenter.x - delta.x, ScreenCenter.y + delta.y); + Invalidate(false); } -- 2.49.0.windows.1 From 25c660b1b7454cdd9181fe167f78d419fd9d3fb9 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Wed, 1 May 2024 12:50:26 -0400 Subject: [PATCH 20/21] Forgot the readme. Version 1.3 should be good now. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ac7ed2b..dcac875 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,9 @@ This is a graphing calculator I made initially for a Calculus project in a day or so. I've written a basic rendering system in Windows Forms that runs on .NET 8.0. Currently, it doesn't have a whole lot of features, but I'll be adding more in the future. Here's currently what it can do: -- Graph an equation (duh). +- Graph standard equations (duh). - There are currently some rendering issues with asymptotes which will be focused on at some point. +- Graph parametric equations. - Integrate and derive equations. - Graph a slope field of a `dy/dx =` style equation. - View a tangent line of an equation. -- 2.49.0.windows.1 From a780e58378809436259d07f5f6a081f5063004b3 Mon Sep 17 00:00:00 2001 From: That_One_Nerd Date: Wed, 1 May 2024 12:58:08 -0400 Subject: [PATCH 21/21] Also forgot to update the package info. *Really* done now. --- Base/Base.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Base/Base.csproj b/Base/Base.csproj index 2301302..54d38db 100644 --- a/Base/Base.csproj +++ b/Base/Base.csproj @@ -12,18 +12,18 @@ True ThatOneNerd.Graphing ThatOneNerd.Graphing - 1.2.0 + 1.3.0 That_One_Nerd A fairly adept graphing calculator made in Windows Forms. MIT https://github.com/That-One-Nerd/Graphing README.md - graphing;graph;plot;math;calculus;visual;desmos;slope field;slopefield;equation;visualizer + graphing;graph;plot;math;calculus;visual;desmos;slope field;slopefield;equation;visualizer;parametric equation;parametric;difference;tangent MIT True snupkg View the GitHub release for the changelog: -https://github.com/That-One-Nerd/Graphing/releases/tag/1.2.0 +https://github.com/That-One-Nerd/Graphing/releases/tag/1.3.0 -- 2.49.0.windows.1