Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6e90b70
Split UnitValue into two structs: Relative and AbsoluteSize
Exanite Sep 17, 2025
6950b8a
Add Paper.SetPointUnitScale()
Exanite Sep 17, 2025
5dcc305
Update implementation code to use ScalingSettings struct
Exanite Sep 17, 2025
d08f3ef
Explicitly specify size types
Exanite Sep 17, 2025
67208bf
Implement font scaling
Exanite Sep 17, 2025
d873339
Rename Absolute/RelativeSize to Absolute/RelativeUnit
Exanite Sep 17, 2025
215bb4d
Explicitly use UnitValue.Points
Exanite Sep 17, 2025
38567cc
Apply scaling to word and letter spacing
Exanite Sep 17, 2025
e608b76
Apply scaling to TranslateX/Y
Exanite Sep 17, 2025
15cd6da
Apply scaling to rounding
Exanite Sep 17, 2025
bb4a49c
Add interpolation for absolute units
Exanite Sep 17, 2025
5c185e3
Implement lerping for Rounding struct
Exanite Sep 17, 2025
fbad8ad
Use in keyword consistently with AbsoluteUnit
Exanite Sep 17, 2025
72a0bb9
Apply scaling to box shadow
Exanite Sep 17, 2025
1a2c018
Fix incorrect toggle scaling in PaperDemo
Exanite Sep 17, 2025
5539835
Add Paper.Points helper function
Exanite Sep 17, 2025
d031893
Rename PointUnitScale to ContentScale
Exanite Sep 17, 2025
c9596ad
Scale scroll speed by content scale
Exanite Sep 17, 2025
a01cc3d
Scale a few hardcoded pixel values by content scale
Exanite Sep 17, 2025
dfb0e6e
Move new types into their correct locations in the codebase
Exanite Sep 17, 2025
baa5aae
Add Prowl license header to RelativeUnit
Exanite Sep 18, 2025
e598183
Cleanup
Exanite Sep 18, 2025
e6c596b
Apply scaling to scrollbars
Exanite Sep 18, 2025
3095fc1
Use UnitValue.Points and ToPx instead of scaling pixel values manually
Exanite Sep 18, 2025
2656f18
Expose scaling settings as a public property to allow users to call A…
Exanite Sep 18, 2025
a599e33
Add content scaling to README features list
Exanite Sep 18, 2025
928471a
Fix typo in README: "any where" -> "anywhere
Exanite Sep 18, 2025
04291bc
Edit README
Exanite Sep 18, 2025
dec1237
Use in keyword with ScalingSettings parameters
Exanite Sep 18, 2025
0cbd3dc
Fix scrollbar constants being writable
Exanite Sep 18, 2025
83a474d
Expose ContentScale directly as a convenience property
Exanite Sep 18, 2025
acd2fd0
Provide ScalingSettings when using AddActionsElement to facilitate sc…
Exanite Sep 18, 2025
faf4042
Update Samples Canvas commands to scale properly
Exanite Sep 18, 2025
fcc506b
Add constructor for ScalingSettings struct
Exanite Sep 18, 2025
70ed5c7
Update test cases
Exanite Sep 18, 2025
0f59de3
Document that the Transform property and Canvas commands are not scal…
Exanite Sep 18, 2025
d08e3be
Merge branch 'main' into dev/ui-scaling-3
Exanite Sep 18, 2025
6fff837
Add default constructor ScalingSettings to prevent accidental default…
Exanite Sep 18, 2025
288dcca
Update Origami to scale property
Exanite Sep 18, 2025
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
55 changes: 28 additions & 27 deletions Origami/SpinnerUtil.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Drawing;

using Prowl.PaperUI.LayoutEngine;
using Prowl.Quill;
using Prowl.Vector;

Expand Down Expand Up @@ -30,15 +31,15 @@ public static class SpinnerUtil
/// </summary>
public struct SpinnerConfig
{
/// <summary>Size of the spinner in pixels.</summary>
public double Size { get; set; }
/// <summary>Size of the spinner.</summary>
public AbsoluteUnit Size { get; set; }

/// <summary>Color of the spinner.</summary>
public Color Color { get; set; }

/// <summary>Width of the spinner stroke.</summary>
public double StrokeWidth { get; set; }
public AbsoluteUnit StrokeWidth { get; set; }

/// <summary>Animation speed multiplier (1.0 = normal speed).</summary>
public double Speed { get; set; }

Expand Down Expand Up @@ -77,20 +78,20 @@ public struct SpinnerConfig
/// <param name="paper">Paper UI instance</param>
/// <param name="config">Spinner configuration</param>
/// <returns>Action that can be used with AddActionElement</returns>
public static Action<Canvas, Rect> CreateSpinner(Paper paper, SpinnerConfig config)
public static Action<Canvas, Rect, ScalingSettings> CreateSpinner(Paper paper, SpinnerConfig config)
{
return (canvas, rect) => {
return (canvas, rect, scalingSettings) => {
var centerX = rect.x + rect.width / 2;
var centerY = rect.y + rect.height / 2;
var radius = config.Size / 2;
var radius = config.Size.ToPx(scalingSettings) / 2;

// Calculate rotation based on time
var time = paper.Time;
var rotation = (time * config.Speed * 2) % (Math.PI * 2); // Full rotation every second at speed 1.0
var rotDegrees = MathD.ToDeg(rotation);

canvas.SaveState();

// Move to center and rotate
canvas.TransformBy(Transform2D.CreateTranslation(centerX, centerY));
canvas.TransformBy(Transform2D.CreateRotate(rotDegrees));
Expand All @@ -99,7 +100,7 @@ public static Action<Canvas, Rect> CreateSpinner(Paper paper, SpinnerConfig conf
canvas.BeginPath();
canvas.Arc(0, 0, radius, 0, Math.PI * 1.5); // 3/4 circle
canvas.SetStrokeColor(config.Color);
canvas.SetStrokeWidth(config.StrokeWidth);
canvas.SetStrokeWidth(config.StrokeWidth.ToPx(scalingSettings));
canvas.Stroke();

canvas.RestoreState();
Expand All @@ -113,7 +114,7 @@ public static Action<Canvas, Rect> CreateSpinner(Paper paper, SpinnerConfig conf
/// <param name="theme">Origami theme for color consistency</param>
/// <param name="size">Size variant</param>
/// <returns>Action that can be used with AddActionElement</returns>
public static Action<Canvas, Rect> CreateThemedSpinner(Paper paper, OrigamiTheme theme, OrigamiSize size = OrigamiSize.Medium)
public static Action<Canvas, Rect, ScalingSettings> CreateThemedSpinner(Paper paper, OrigamiTheme theme, OrigamiSize size = OrigamiSize.Medium)
{
var config = size switch
{
Expand All @@ -135,7 +136,7 @@ public static Action<Canvas, Rect> CreateThemedSpinner(Paper paper, OrigamiTheme
/// <param name="color">Color for the spinner</param>
/// <param name="size">Size variant</param>
/// <returns>Action that can be used with AddActionElement</returns>
public static Action<Canvas, Rect> CreateColoredSpinner(Paper paper, Color color, OrigamiSize size = OrigamiSize.Medium)
public static Action<Canvas, Rect, ScalingSettings> CreateColoredSpinner(Paper paper, Color color, OrigamiSize size = OrigamiSize.Medium)
{
var config = size switch
{
Expand All @@ -154,29 +155,29 @@ public static Action<Canvas, Rect> CreateColoredSpinner(Paper paper, Color color
/// <param name="paper">Paper UI instance</param>
/// <param name="config">Spinner configuration</param>
/// <returns>Action that can be used with AddActionElement</returns>
public static Action<Canvas, Rect> CreateDotsSpinner(Paper paper, SpinnerConfig config)
public static Action<Canvas, Rect, ScalingSettings> CreateDotsSpinner(Paper paper, SpinnerConfig config)
{
return (canvas, rect) => {
return (canvas, rect, scalingSettings) => {
var centerX = rect.x + rect.width / 2;
var centerY = rect.y + rect.height / 2;
var time = paper.Time * config.Speed;

// Three dots with staggered animation
var dotSize = config.Size / 6;
var spacing = config.Size / 3;
var dotSize = config.Size.ToPx(scalingSettings) / 6;
var spacing = config.Size.ToPx(scalingSettings) / 3;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only thing im not a huge fan of, having the user have to handle the scaling settings in custom drawing manually. Would making a wrapper around a Canvas that just converts the RelativeUnits and AbsoluteUnits to pixels using the scaling setting for the user work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review!

The Canvas API is fairly large, so I didn't consider wrapping it, but it definitely is doable.
The main cost of this is that we have to remember to update the wrapper if the Canvas API changes.

If a Canvas wrapper is preferred, I can look into implementing this tomorrow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of a wrapper, we could have a Cast for AbsoluteUnit to a number with the scaling settings when its in the context of a drawing action?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see what you mean. Casts don't have parameters and would have to grab the scaling settings from a static variable, which seems too "magical" to me.

Also, going back to the Canvas wrapper idea, I don't think it works either... at least not fully.
AbsoluteUnits default to Points (due to the implicit cast). This is highly beneficial because it ensures UIs scale automatically in most cases.
Rect is in pixels, since I did not change that.
Transform2D is also in pixels for the same reason.

If someone passes a double from a Rect to the Canvas wrapper API, then it's going to be assumed to be in Points, which is incorrect. More information is needed in this case, but more information means manual intervention.

The cleanest option I see is to change Paper to work entirely in terms of Points (I believe web browsers and Avalonia both do this), and make Pixels the exceptional unit (which is technically already is, since it is not the default). This would involve creating the Canvas wrapper or changing Quill itself, ensuring Paper uses it for everything, and update Paper's coordinate system to use Points (Rect, Transform2D, input, resolution).

This would also mean removing AbsoluteUnit again, and just using fractional values to represent physical pixels.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modifying Quill might make sense, since people might need scaling over there as well anyway.
Just make everything by default be Points, and yeah pixels just a Fraction of a point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with closing this PR and going with the modify Quill approach. I don't think any code in this PR helps with the new approach. I can't say I'll work on it anytime too soon though, since the current solution works for my needs.

I think the main requirements are to ensure that font resolution scales correctly and that enough vertices are output to maintain per-pixel detail. It already mostly works when I modified the projection matrix and resolution that Paper uses (this was the hacky approach I was using before).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets leave this PR open for anyone interested, its still valid. Ill look into adding it into Quill when i get a chance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good to me. Thanks!


canvas.SaveState();

for (int i = 0; i < 3; i++)
{
var x = centerX + (i - 1) * spacing;
var y = centerY;

// Animate opacity based on time and dot index
var animationOffset = i * 0.3; // Stagger the animation
var opacity = (Math.Sin(time * 3 + animationOffset) + 1) / 2; // 0 to 1
opacity = Math.Max(0.3, opacity); // Minimum visibility

var dotColor = Color.FromArgb((int)(255 * opacity), config.Color);

canvas.BeginPath();
Expand All @@ -195,23 +196,23 @@ public static Action<Canvas, Rect> CreateDotsSpinner(Paper paper, SpinnerConfig
/// <param name="paper">Paper UI instance</param>
/// <param name="config">Spinner configuration</param>
/// <returns>Action that can be used with AddActionElement</returns>
public static Action<Canvas, Rect> CreatePulseSpinner(Paper paper, SpinnerConfig config)
public static Action<Canvas, Rect, ScalingSettings> CreatePulseSpinner(Paper paper, SpinnerConfig config)
{
return (canvas, rect) => {
return (canvas, rect, scalingSettings) => {
var centerX = rect.x + rect.width / 2;
var centerY = rect.y + rect.height / 2;
var time = paper.Time * config.Speed;

// Pulsing radius
var baseRadius = config.Size / 3;
var baseRadius = config.Size.ToPx(scalingSettings) / 3;
var pulseRadius = baseRadius + (Math.Sin(time * 4) + 1) / 2 * baseRadius * 0.5;

// Pulsing opacity
var opacity = (Math.Sin(time * 4) + 1) / 2 * 0.7 + 0.3; // 0.3 to 1.0
var pulseColor = Color.FromArgb((int)(255 * opacity), config.Color);

canvas.SaveState();

canvas.BeginPath();
canvas.Circle(centerX, centerY, pulseRadius);
canvas.SetFillColor(pulseColor);
Expand Down
19 changes: 10 additions & 9 deletions Paper/BoxShadow.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Drawing;
using Prowl.PaperUI.LayoutEngine;

namespace Prowl.PaperUI
{
Expand All @@ -7,13 +8,13 @@ namespace Prowl.PaperUI
/// </summary>
public struct BoxShadow
{
public double OffsetX;
public double OffsetY;
public double Blur;
public double Spread;
public AbsoluteUnit OffsetX;
public AbsoluteUnit OffsetY;
public AbsoluteUnit Blur;
public AbsoluteUnit Spread;
public Color Color;

public BoxShadow(double offsetX, double offsetY, double blur, double spread, Color color)
public BoxShadow(AbsoluteUnit offsetX, AbsoluteUnit offsetY, AbsoluteUnit blur, AbsoluteUnit spread, Color color)
{
OffsetX = offsetX;
OffsetY = offsetY;
Expand Down Expand Up @@ -43,10 +44,10 @@ Color LerpColor(Color a, Color b)
}

return new BoxShadow(
start.OffsetX + (end.OffsetX - start.OffsetX) * t,
start.OffsetY + (end.OffsetY - start.OffsetY) * t,
start.Blur + (end.Blur - start.Blur) * t,
start.Spread + (end.Spread - start.Spread) * t,
AbsoluteUnit.Lerp(start.OffsetX, end.OffsetX, t),
AbsoluteUnit.Lerp(start.OffsetY, end.OffsetY, t),
AbsoluteUnit.Lerp(start.Blur, end.Blur, t),
AbsoluteUnit.Lerp(start.Spread, end.Spread, t),
LerpColor(start.Color, end.Color)
);
}
Expand Down
Loading