From 763d7744be8e4bac351f12b87ff747d698c7b4ac Mon Sep 17 00:00:00 2001 From: Sebastian Hedtrich Date: Thu, 17 Sep 2026 13:59:08 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Circle/RoundedRectangle-Zeichenbefehle?= =?UTF-8?q?=20im=20Canvas-Backend,=20Fehlzeitenkalender-Kacheln=20gr=C3=B6?= =?UTF-8?q?=C3=9Fer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neue DrawingCommand-Primitives DrawCircle und DrawRoundedRectangle (SVG-Rendering plus FLOWDRAWBOX-Paginierung). Anwesenheitskalender nutzt RoundedRectangle für die Tageskacheln und verkleinert Monatsabstand/Kachel-Innenabstand, damit die Kacheln den Rasterplatz sichtbar besser ausfüllen. Co-Authored-By: Claude Sonnet 5 --- ...StudentAttendanceCalendarDrawingBuilder.cs | 14 ++++++++++--- LehrerApp.Templating.Tests/TemplatingTests.cs | 2 ++ LehrerApp.Templating/Model.cs | 11 ++++++++++ LehrerApp.Templating/QuestTemplateRenderer.cs | 20 +++++++++++++++++-- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/LehrerApp.Desktop/Services/StudentAttendanceCalendarDrawingBuilder.cs b/LehrerApp.Desktop/Services/StudentAttendanceCalendarDrawingBuilder.cs index 39d41ce..a2dc478 100644 --- a/LehrerApp.Desktop/Services/StudentAttendanceCalendarDrawingBuilder.cs +++ b/LehrerApp.Desktop/Services/StudentAttendanceCalendarDrawingBuilder.cs @@ -44,11 +44,18 @@ public static class StudentAttendanceCalendarDrawingBuilder // würde am rechten Rand abgeschnitten (SVG overflow="hidden"). var contentWidth = width; var cellHeight = 10 * scale; - var monthGapX = 10f; + // War 10 - bei 3 Monaten nebeneinander größer als eine einzelne Tageskachel und zog damit + // spürbar Platz von den Kacheln ab, ohne selbst als Inhalt wahrgenommen zu werden. + var monthGapX = 6f; var first = options.NormalizedStartMonth; var monthCount = options.NormalizedMonthCount; var monthWidth = (contentWidth - monthGapX * (monthCount - 1)) / monthCount; var cellWidth = monthWidth / 7; + // Kachel-Zwischenraum (Trennung zu Nachbarzellen) deutlich knapper als vorher (war "scale", + // also bis zu 1mm auf jeder Seite - bei einer ~7mm breiten Zelle ein gutes Viertel reiner + // Leerraum). Die Kachel selbst füllt dadurch ihren Rasterplatz sichtbar besser aus. + var cellInset = scale * .4f; + var cellCornerRadius = Math.Min(cellWidth, cellHeight) * .2f; var commands = new List(); var y = 0f; commands.Add(new DrawStringEx(0, y, 14 * scale, contentWidth, "Anwesenheit", @@ -86,8 +93,9 @@ public static class StudentAttendanceCalendarDrawingBuilder var row = index / 7; var x = xOffset + column * cellWidth; var cellY = monthY + row * cellHeight; - commands.Add(new DrawRectangle(x + scale, cellY, cellWidth - 2 * scale, cellHeight - scale, - "#D1D5DB", .35f, day.HasSignal ? day.SignalColorHex : "#FFFFFF")); + commands.Add(new DrawRoundedRectangle(x + cellInset, cellY, cellWidth - 2 * cellInset, + cellHeight - cellInset, cellCornerRadius, "#D1D5DB", .35f, + day.HasSignal ? day.SignalColorHex : "#FFFFFF")); commands.Add(new DrawStringEx(x, cellY + scale, cellHeight - 2 * scale, cellWidth, day.HasSignal ? day.SignalCode : day.DayNumber, DrawingTextAlignment.AlignCenter, 7 * scale, Color: day.HasSignal ? "#FFFFFF" : "#374151", Bold: day.HasSignal)); diff --git a/LehrerApp.Templating.Tests/TemplatingTests.cs b/LehrerApp.Templating.Tests/TemplatingTests.cs index ce4e30b..cae6111 100644 --- a/LehrerApp.Templating.Tests/TemplatingTests.cs +++ b/LehrerApp.Templating.Tests/TemplatingTests.cs @@ -85,6 +85,8 @@ public sealed class TemplatingTests : IDisposable new Dictionary()); var drawing = new DrawingValue( [new DrawRectangle(0, 0, 100, 60, "#1D4ED8", 1, "#EFF6FF"), + new DrawRoundedRectangle(2, 2, 30, 15, 3, "#1D4ED8", 1, "#EFF6FF"), + new DrawCircle(90, 10, 6, "#1D4ED8", 1, "#EFF6FF"), new DrawString(5, 5, "Externer Inhalt", 11, Bold: true), new MoveTo(5, 25), new LineTo(95, 25, "#DC2626", 1.5f), new DrawLine(5, 35, 95, 50, "#059669", 1)], 60); diff --git a/LehrerApp.Templating/Model.cs b/LehrerApp.Templating/Model.cs index 587452d..ab2f27a 100644 --- a/LehrerApp.Templating/Model.cs +++ b/LehrerApp.Templating/Model.cs @@ -30,6 +30,10 @@ public sealed record DrawLine(float X1, float Y1, float X2, float Y2, string Color = "#000000", float StrokeWidth = 1) : DrawingCommand; public sealed record DrawRectangle(float X, float Y, float Width, float Height, string StrokeColor = "#000000", float StrokeWidth = 1, string FillColor = "none") : DrawingCommand; +public sealed record DrawRoundedRectangle(float X, float Y, float Width, float Height, float CornerRadius, + string StrokeColor = "#000000", float StrokeWidth = 1, string FillColor = "none") : DrawingCommand; +public sealed record DrawCircle(float CenterX, float CenterY, float Radius, + string StrokeColor = "#000000", float StrokeWidth = 1, string FillColor = "none") : DrawingCommand; public sealed record DrawImage(float X, float Y, float Width, float Height, byte[] Data, string MimeType) : DrawingCommand; /// Declarative drawing supplied by an external data provider. Coordinates use the template layout unit; font sizes use points. public sealed record DrawingValue(IReadOnlyList Commands, float ContentHeight) @@ -56,6 +60,13 @@ public sealed class DrawingCanvas public void DrawRectangle(float x, float y, float width, float height, string strokeColor = "#000000", float strokeWidth = 1, string fillColor = "none") => _commands.Add(new LehrerApp.Templating.DrawRectangle(x, y, width, height, strokeColor, strokeWidth, fillColor)); + public void DrawRoundedRectangle(float x, float y, float width, float height, float cornerRadius, + string strokeColor = "#000000", float strokeWidth = 1, string fillColor = "none") => + _commands.Add(new LehrerApp.Templating.DrawRoundedRectangle(x, y, width, height, cornerRadius, + strokeColor, strokeWidth, fillColor)); + public void DrawCircle(float centerX, float centerY, float radius, string strokeColor = "#000000", + float strokeWidth = 1, string fillColor = "none") => + _commands.Add(new LehrerApp.Templating.DrawCircle(centerX, centerY, radius, strokeColor, strokeWidth, fillColor)); public void DrawImage(float x, float y, float width, float height, byte[] data, string mimeType) => _commands.Add(new LehrerApp.Templating.DrawImage(x, y, width, height, data, mimeType)); } diff --git a/LehrerApp.Templating/QuestTemplateRenderer.cs b/LehrerApp.Templating/QuestTemplateRenderer.cs index d41ec4c..d11701a 100644 --- a/LehrerApp.Templating/QuestTemplateRenderer.cs +++ b/LehrerApp.Templating/QuestTemplateRenderer.cs @@ -577,15 +577,18 @@ internal static class DrawingElementRenderer private static float? CommandY(DrawingCommand command) => command switch { DrawString s => s.Y, DrawStringEx s => s.Y, MoveTo m => m.Y, LineTo l => l.Y, - DrawRectangle r => r.Y, DrawImage i => i.Y, DrawLine ln => Math.Min(ln.Y1, ln.Y2), _ => null, + DrawRectangle r => r.Y, DrawRoundedRectangle r => r.Y, DrawImage i => i.Y, + DrawLine ln => Math.Min(ln.Y1, ln.Y2), DrawCircle c => c.CenterY - c.Radius, _ => null, }; private static DrawingCommand ShiftY(DrawingCommand command, float dy) => command switch { DrawString s => s with { Y = s.Y - dy }, DrawStringEx s => s with { Y = s.Y - dy }, MoveTo m => m with { Y = m.Y - dy }, LineTo l => l with { Y = l.Y - dy }, - DrawRectangle r => r with { Y = r.Y - dy }, DrawImage i => i with { Y = i.Y - dy }, + DrawRectangle r => r with { Y = r.Y - dy }, DrawRoundedRectangle r => r with { Y = r.Y - dy }, + DrawImage i => i with { Y = i.Y - dy }, DrawLine ln => ln with { Y1 = ln.Y1 - dy, Y2 = ln.Y2 - dy }, + DrawCircle c => c with { CenterY = c.CenterY - dy }, _ => command, }; @@ -649,6 +652,19 @@ internal static class DrawingElementRenderer svg.Append(CultureInfo.InvariantCulture, $""); break; + case DrawRoundedRectangle rectangle: + Validate(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, rectangle.CornerRadius, + rectangle.StrokeWidth); + Positive(rectangle.Width, rectangle.Height, rectangle.CornerRadius, rectangle.StrokeWidth); + svg.Append(CultureInfo.InvariantCulture, + $""); + break; + case DrawCircle circle: + Validate(circle.CenterX, circle.CenterY, circle.Radius, circle.StrokeWidth); + Positive(circle.Radius, circle.StrokeWidth); + svg.Append(CultureInfo.InvariantCulture, + $""); + break; case DrawString text: Validate(text.X, text.Y, text.FontSize); Positive(text.FontSize);