Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6c0bc80
test(mono): run complex chart acceptance inside visible desktop window
PawelTroka Oct 26, 2026
da10c95
test(mono): capture real X11 complex chart after Draw
PawelTroka Oct 26, 2026
d52c437
test(mono): exercise five actual X11 Draw clicks at default quality
PawelTroka Oct 26, 2026
0a66a06
ci(mono): install XTEST native mouse dependency for visible chart checks
PawelTroka Oct 26, 2026
e5c30ba
test(mono): verify production executable paints complex chart on real…
PawelTroka Oct 26, 2026
46bd10d
ci(mono): clear stale production complex screenshots before acceptance
PawelTroka Oct 26, 2026
ba91dc4
fix(mono): make selected complex chart the frontmost native WinForms …
PawelTroka Oct 26, 2026
7a532b1
test(mono): require selected complex plot to own front X11 z-order
PawelTroka Oct 26, 2026
e0e2b79
test(mono): cover visible redraw expression resize modes clear and re…
PawelTroka Oct 26, 2026
7f8e54d
test(mono): pass desktop error recorder into complex chart acceptance
PawelTroka Oct 26, 2026
4f7f164
fix(chart): surface async complex render errors without crashing the UI
PawelTroka Oct 26, 2026
bd312f7
test(mono): verify worker-render exception is visible and later Draw …
PawelTroka Oct 26, 2026
20bb917
ci(mono): remove all stale native complex UI screenshots before valid…
PawelTroka Oct 26, 2026
825c5b7
fix(mono): include ComplexChartUiAcceptance.cs in developer solution …
PawelTroka Oct 26, 2026
cf82022
test(mono): exercise production complex Draw with native X11 click an…
PawelTroka Oct 26, 2026
7965e05
fix(mono): retain Windows UI exception contract for current complex r…
PawelTroka Oct 26, 2026
9549148
test(mono): avoid duplicate X11 imports when native 3D support is int…
PawelTroka Oct 26, 2026
35e8c5c
test(mono): distinguish stalled complex redraw from image publication…
PawelTroka Oct 26, 2026
eeb395e
fix(mono): publish complex chart samples via UI timer instead of stal…
PawelTroka Oct 27, 2026
82e576d
test(mono): report production worker completion and GUI polling state
PawelTroka Oct 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/mono-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Install the actual Mono and X11 runtime
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends mono-devel xvfb xauth libgdiplus libgsl-dev fonts-dejavu-core
sudo apt-get install -y --no-install-recommends mono-devel xvfb xauth libxtst6 libgdiplus libgsl-dev fonts-dejavu-core
- name: Build the opt-in Framework graph and run desktop acceptance
run: ./build.sh --target=Mono-Validate
- name: Retain runtime evidence even on failure
Expand Down
134 changes: 132 additions & 2 deletions Computator.NET.Charting/ComplexCharting/ComplexChart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ private void OnPropertyChanged(string propertyName)
// private Task RedrawTask;
private ComplexPoint[,] _pointsValues;
private bool _redrawing = false;
#if __MonoCS__
// The real Mono Application.Run pipeline may never dispatch the awaited
// WinForms synchronization-context continuation. Poll completion from
// the UI message loop instead; never access WinForms from the worker.
private System.Windows.Forms.Timer _monoRenderTimer;
private Task<ChartSamples> _monoRenderTask;
private long _monoRenderTaskVersion;
#endif
public string LastRenderError { get; private set; }
private long _redrawVersion;
private Function function;
private Bitmap image = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
Expand Down Expand Up @@ -290,6 +299,22 @@ protected override void Dispose(bool disposing)
{
if (disposing)
{
#if __MonoCS__
// A task may finish after the control has closed; release its
// independent sample bitmap without posting into a dead form.
_monoRenderTimer?.Stop();
_monoRenderTimer?.Dispose();
_monoRenderTimer = null;
var pending = _monoRenderTask;
_monoRenderTask = null;
if (pending != null)
pending.ContinueWith(t =>
{
if (t.Status == TaskStatus.RanToCompletion)
t.Result.Bitmap.Dispose();
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
#endif
++_redrawVersion;
function = null;
_pointsValues = null;
Expand Down Expand Up @@ -352,6 +377,7 @@ private void _MouseClick(object s, MouseEventArgs e)

public void AddFunction(Function Fz)
{
LastRenderError = null;
function = Fz;
Title = Fz.Name;
Redraw();
Expand Down Expand Up @@ -451,6 +477,7 @@ private Bitmap ComposeImage(Image source, int width, int height)

public void ClearAll()
{
LastRenderError = null;
++_redrawVersion;
function = null;
_pointsValues = null;
Expand All @@ -475,6 +502,98 @@ public void ShowPlotDialog()
plotForm.Show();
}

#if __MonoCS__
public void Redraw()
{
if (IsDisposed || Disposing)
return;
++_redrawVersion;
if (function == null || DrawWidth <= 0 || DrawHeight <= 0 || _redrawing)
return;
_redrawing = true;
Cursor = Cursors.WaitCursor;
if (_monoRenderTimer == null)
{
_monoRenderTimer = new System.Windows.Forms.Timer { Interval = 35 };
_monoRenderTimer.Tick += MonoRenderTimerTick;
}
StartMonoRenderTask();
}

private void StartMonoRenderTask()
{
_monoRenderTaskVersion = _redrawVersion;
var width = DrawWidth;
var height = DrawHeight;
// Use the default scheduler and a dedicated worker on Mono. This
// avoids starvation by UI-associated custom schedulers and never
// executes the expensive sampling on the WinForms thread.
_monoRenderTask = Task.Factory.StartNew(
() => CalculateValuesAndColors(width, height), CancellationToken.None,
TaskCreationOptions.LongRunning, TaskScheduler.Default);
_monoRenderTimer.Start();
}

private void MonoRenderTimerTick(object sender, EventArgs e)
{
var task = _monoRenderTask;
if (task == null || !task.IsCompleted)
return;
_monoRenderTask = null;
var version = _monoRenderTaskVersion;
try
{
if (task.Status == TaskStatus.RanToCompletion)
{
var samples = task.Result;
if (!IsDisposed && !Disposing && function != null &&
version == _redrawVersion)
{
var oldImage = image;
image = samples.Bitmap;
_pointsValues = samples.PointsValues;
oldImage?.Dispose();
Invalidate();
}
else
{
samples.Bitmap.Dispose();
}
}
else if (task.IsFaulted && !IsDisposed && !Disposing &&
function != null && version == _redrawVersion)
{
var error = task.Exception.GetBaseException();
Logger.Error(error, "Complex chart asynchronous redraw failed.");
LastRenderError = error.Message;
Invalidate();
}
}
catch (Exception error)
{
// Keep the actual production message loop alive on Mono.
Logger.Error(error, "Complex chart result publication failed.");
LastRenderError = error.Message;
if (!IsDisposed && !Disposing)
Invalidate();
}

if (!IsDisposed && !Disposing && function != null &&
DrawWidth > 0 && DrawHeight > 0 && version != _redrawVersion)
{
// Coalesce edits, Clear, mode changes and resizing while a
// previous evaluation is still running. Never publish stale
// samples or stale errors from superseded work.
StartMonoRenderTask();
return;
}

_monoRenderTimer.Stop();
_redrawing = false;
if (!IsDisposed && !Disposing)
Cursor = Cursors.Default;
}
#else
public async void Redraw()
{
if (IsDisposed || Disposing)
Expand All @@ -498,10 +617,10 @@ public async void Redraw()
}
catch (Exception) when (function == null || IsDisposed || version != _redrawVersion)
{
// A discarded evaluation must not show an error after a new
// plot, clear or close. Current evaluation errors still propagate.
// Superseded evaluations should never report an error.
continue;
}

if (function == null || IsDisposed || version != _redrawVersion)
{
samples.Bitmap.Dispose();
Expand All @@ -523,9 +642,20 @@ public async void Redraw()
}
}

#endif

protected override void OnPaint(PaintEventArgs pe)
{
PaintOn(pe.Graphics, ClientRectangle, image);
if (!string.IsNullOrWhiteSpace(LastRenderError))
{
var bounds = Rectangle.Inflate(ClientRectangle, -12, -12);
if (bounds.Width > 0 && bounds.Height > 0)
TextRenderer.DrawText(pe.Graphics, "Complex chart rendering failed: " + LastRenderError,
Font, new Rectangle(bounds.Left, bounds.Top, bounds.Width, Math.Min(90, bounds.Height)),
Color.DarkRed, BackColor,
TextFormatFlags.WordBreak | TextFormatFlags.Left | TextFormatFlags.Top);
}
}


Expand Down
29 changes: 28 additions & 1 deletion Computator.NET.Desktop/Views/ChartingView.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Computator.NET.Charting.Chart3D.UI;
using Computator.NET.Charting.ComplexCharting;
using Computator.NET.Charting.RealCharting;
using Computator.NET.Core.Abstract.Views;
using Computator.NET.DataTypes;
using Computator.NET.DataTypes.Charts;
#if __MonoCS__
using Computator.NET.DataTypes.Events;
#endif

namespace Computator.NET.Desktop.Views
{
public partial class ChartingView : UserControl, IChartingView
{
#if __MonoCS__
private readonly Action<CalculationsModeChangedEvent> _showMonoChart;
#endif
public ChartingView(ChartAreaValuesView chartAreaValuesView, Chart2D chart2D, ComplexChart complexChart, Chart3DControl chart3D) : this()
{
chartAreaValuesView.Dock = DockStyle.Right;
Expand Down Expand Up @@ -40,6 +47,26 @@ public ChartingView(ChartAreaValuesView chartAreaValuesView, Chart2D chart2D, Co
#endif
(Control) chartAreaValuesView,
});
#if __MonoCS__
// All three chart controls are docked Fill in the same panel.
// Hiding a sibling does not reliably lower its native X11 window
// on every Mono/WinForms backend. Select the active chart's actual
// z-order as well as its Visible flag (set by the presenter).
_showMonoChart = mode =>
{
IChart active;
if (!Charts.TryGetValue(mode.CalculationsMode, out active))
return;
var control = active as Control;
if (control == null || control.IsDisposed)
return;
control.BringToFront();
control.Invalidate();
};
EventAggregator.Instance.Subscribe(_showMonoChart);
Disposed += (sender, args) =>
EventAggregator.Instance.Unsubscribe(_showMonoChart);
#endif
}

private ChartingView()
Expand Down
1 change: 1 addition & 0 deletions Computator.NET.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
<File Path="build/mono/LegacyFixtureWriter.cs" />
<File Path="build/mono/SmokeTests/CompatibilityTests.cs" />
<File Path="build/mono/SmokeTests/Computator.NET.Mono.SmokeTests.csproj" />
<File Path="build/mono/SmokeTests/ComplexChartUiAcceptance.cs" />
<File Path="build/mono/SmokeTests/Program.cs" />
<File Path="build/mono/StartupProbe/Computator.NET.Mono.StartupProbe.csproj" />
<File Path="build/mono/StartupProbe/Program.cs" />
Expand Down
Loading
Loading