Skip to content

Commit 60bc554

Browse files
committed
Add Stars
1 parent 24be475 commit 60bc554

File tree

10 files changed

+12458
-12027
lines changed

10 files changed

+12458
-12027
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ Install-Package DevWinUI
135135
## 🔥 DevWinUI.Controls 🔥
136136
### ⚡ What’s Inside? ⚡
137137

138+
- ✨ Stars
138139
- ✨ BannerView
139140
- ✨ AudioWave
140141
- ✨ SpectrumVisualizer
@@ -314,6 +315,9 @@ Install-Package DevWinUI.ContextMenu
314315

315316
## 🕰️ History 🕰️
316317

318+
### Stars
319+
![Stars](https://raw.githubusercontent.com/ghost1372/DevWinUI-Resources/refs/heads/main/DevWinUI-Docs/Stars.gif)
320+
317321
### BannerView
318322
![BannerView](https://raw.githubusercontent.com/ghost1372/DevWinUI-Resources/refs/heads/main/DevWinUI-Docs/BannerView.gif)
319323

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
using Windows.Devices.Sensors;
2+
3+
namespace DevWinUI;
4+
5+
public partial class Stars : Control
6+
{
7+
private const string PART_Sky = "PART_Sky";
8+
private Border sky;
9+
private readonly Random _random = new Random((int)DateTime.Now.Ticks);
10+
11+
private CompositionSurfaceBrush _circleBrush;
12+
private Compositor _compositor;
13+
private ContainerVisual _skyVisual;
14+
15+
private Inclinometer _inclinometer;
16+
private uint _desiredReportInterval;
17+
private ExpressionAnimation _skyViusalOffsetExpressionAnimation;
18+
private CompositionPropertySet _reading;
19+
private float SkyVisualRadius => _skyVisual.Size.X / 2;
20+
private bool _isUnloading;
21+
public Stars()
22+
{
23+
DefaultStyleKey = typeof(Stars);
24+
25+
SetupInclinometer();
26+
27+
RegisterPropertyChangedCallback(VisibilityProperty, OnVisibilityChanged);
28+
}
29+
30+
protected override void OnApplyTemplate()
31+
{
32+
base.OnApplyTemplate();
33+
34+
sky = GetTemplateChild(PART_Sky) as Border;
35+
36+
InitializeCompositionVariables();
37+
CreateStarsVisuals();
38+
StartSkyCanvasStartupAnimations();
39+
40+
Unloaded -= OnUnloaded;
41+
Unloaded += OnUnloaded;
42+
}
43+
44+
private void OnUnloaded(object sender, RoutedEventArgs e)
45+
{
46+
_isUnloading = true;
47+
StopInclinometer();
48+
49+
_skyVisual?.StopAnimation("Offset");
50+
}
51+
private void OnVisibilityChanged(DependencyObject sender, DependencyProperty dp)
52+
{
53+
if (Visibility == Visibility.Visible)
54+
{
55+
RunInclinometer();
56+
}
57+
else
58+
{
59+
StopInclinometer();
60+
}
61+
}
62+
63+
public string StarUri
64+
{
65+
get => (string)GetValue(StarUriProperty);
66+
set => SetValue(StarUriProperty, value);
67+
}
68+
69+
public static readonly DependencyProperty StarUriProperty =
70+
DependencyProperty.Register(nameof(StarUri), typeof(string), typeof(Stars),
71+
new PropertyMetadata(null, OnStarUriChanged));
72+
73+
private static void OnStarUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
74+
{
75+
var ctl = (Stars)d;
76+
if (ctl != null)
77+
{
78+
ctl.InitializeCompositionVariables();
79+
}
80+
}
81+
82+
public double SkyVisualAreaRatio
83+
{
84+
get => (double)GetValue(SkyVisualAreaRatioProperty);
85+
set => SetValue(SkyVisualAreaRatioProperty, value);
86+
}
87+
88+
public static readonly DependencyProperty SkyVisualAreaRatioProperty =
89+
DependencyProperty.Register(nameof(SkyVisualAreaRatio), typeof(double), typeof(Stars), new PropertyMetadata(1.2d, OnSkyVisualAreaRatioChanged));
90+
private static void OnSkyVisualAreaRatioChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
91+
{
92+
var ctl = (Stars)d;
93+
if (ctl != null)
94+
{
95+
ctl.UpdateSkyVisualSize();
96+
}
97+
}
98+
private void UpdateSkyVisualSize()
99+
{
100+
if (_skyVisual == null) return;
101+
102+
UpdateVisual();
103+
}
104+
public int NumberOfSmallTwinklingStars
105+
{
106+
get => (int)GetValue(NumberOfSmallTwinklingStarsProperty);
107+
set => SetValue(NumberOfSmallTwinklingStarsProperty, value);
108+
}
109+
110+
public static readonly DependencyProperty NumberOfSmallTwinklingStarsProperty =
111+
DependencyProperty.Register(nameof(NumberOfSmallTwinklingStars), typeof(int), typeof(Stars),
112+
new PropertyMetadata(600, OnNumberOfStarsChanged));
113+
114+
public int NumberOfBigMovingStars
115+
{
116+
get => (int)GetValue(NumberOfBigMovingStarsProperty);
117+
set => SetValue(NumberOfBigMovingStarsProperty, value);
118+
}
119+
120+
public static readonly DependencyProperty NumberOfBigMovingStarsProperty =
121+
DependencyProperty.Register(nameof(NumberOfBigMovingStars), typeof(int), typeof(Stars),
122+
new PropertyMetadata(150, OnNumberOfStarsChanged));
123+
private static void OnNumberOfStarsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
124+
{
125+
var ctl = (Stars)d;
126+
if (ctl != null)
127+
{
128+
ctl.RecreateStars();
129+
}
130+
}
131+
private void RecreateStars()
132+
{
133+
if (_skyVisual == null) return;
134+
135+
_skyVisual?.Children?.RemoveAll();
136+
CreateStarsVisuals();
137+
}
138+
private async void OnInclinometerReadingChanged(Inclinometer sender, InclinometerReadingChangedEventArgs e)
139+
{
140+
DispatcherQueue.TryEnqueue(() =>
141+
{
142+
_reading.InsertVector3("Offset", new Vector3(e.Reading.RollDegrees, e.Reading.PitchDegrees, 0.0f));
143+
});
144+
}
145+
146+
private void SetupInclinometer()
147+
{
148+
_inclinometer = Inclinometer.GetDefault();
149+
150+
if (_inclinometer != null)
151+
{
152+
// Select a report interval that is both suitable for the purposes of the app and supported by the sensor.
153+
// This value will be used later to activate the sensor.
154+
uint minReportInterval = _inclinometer.MinimumReportInterval;
155+
_desiredReportInterval = minReportInterval > 16 ? minReportInterval : 16;
156+
// Establish the report interval.
157+
_inclinometer.ReportInterval = _desiredReportInterval;
158+
}
159+
}
160+
161+
/// <summary>
162+
/// Restore the default report interval.
163+
/// </summary>
164+
private void RunInclinometer()
165+
{
166+
_inclinometer.ReportInterval = _desiredReportInterval;
167+
_inclinometer.ReadingChanged += OnInclinometerReadingChanged;
168+
}
169+
170+
/// <summary>
171+
/// Reset the default report interval to release resources while the sensor is not in use.
172+
/// </summary>
173+
private void StopInclinometer()
174+
{
175+
_inclinometer?.ReportInterval = 0;
176+
_inclinometer?.ReadingChanged -= OnInclinometerReadingChanged;
177+
}
178+
179+
private void UpdateVisual()
180+
{
181+
var appWindow = WindowHelper.GetAppWindow(this);
182+
var bounds = appWindow.ClientSize;
183+
184+
var maxWindowWidth = (float)bounds.Width;
185+
var maxWindowHeight = (float)bounds.Height;
186+
var maxWindowRadius = Math.Max(maxWindowWidth, maxWindowHeight);
187+
188+
// Setup sky visual's size, position, opacity, etc.
189+
_skyVisual = sky.ContainerVisual();
190+
_skyVisual.Size = new Vector2(maxWindowRadius * (float)SkyVisualAreaRatio);
191+
_skyVisual.Offset = new Vector3(-SkyVisualRadius + maxWindowWidth / 2.0f, -SkyVisualRadius + maxWindowHeight / 2.0f, 0.0f);
192+
_skyVisual.CenterPoint = new Vector3(SkyVisualRadius, SkyVisualRadius, 0.0f);
193+
_skyVisual.Opacity = 0;
194+
}
195+
private void InitializeCompositionVariables()
196+
{
197+
UpdateVisual();
198+
_compositor = _skyVisual.Compositor;
199+
_reading = _compositor.CreatePropertySet();
200+
_reading.InsertVector3("Offset", new Vector3(0.0f, 0.0f, 0.0f));
201+
_circleBrush = _compositor.CreateSurfaceBrush();
202+
203+
if (string.IsNullOrEmpty(StarUri))
204+
{
205+
var assembly = typeof(Stars).Assembly;
206+
var stream = FileHelper.GetFileFromEmbededResourcesOrUri(new Uri("ms-appx:///Assets/Mask/CircleMask.png"), assembly);
207+
_circleBrush.Surface = LoadedImageSurface.StartLoadFromStream(stream.AsRandomAccessStream());
208+
}
209+
else
210+
{
211+
_circleBrush.Surface = LoadedImageSurface.StartLoadFromUri(new Uri(StarUri));
212+
}
213+
214+
if (_inclinometer != null) SetupSkyVisualOffsetExpressionAnimation();
215+
}
216+
217+
private void SetupSkyVisualOffsetExpressionAnimation()
218+
{
219+
// Kick off an expression animation that links the roll & pitch degress to the -offset of the sky canvas visual
220+
// TODO: Need to constraint the offset (total offset < dimension * SkyVisualAreaRatio) with
221+
// CreateConditionalExpressionAnimation once the next mobile build is available.
222+
_skyViusalOffsetExpressionAnimation = _compositor.CreateExpressionAnimation(
223+
"Vector3(SkyVisual.Offset.X - Reading.Offset.X * Sensitivity, SkyVisual.Offset.Y - Reading.Offset.Y * Sensitivity, 0.0f)");
224+
_skyViusalOffsetExpressionAnimation.SetReferenceParameter("SkyVisual", _skyVisual);
225+
_skyViusalOffsetExpressionAnimation.SetReferenceParameter("Reading", _reading);
226+
_skyViusalOffsetExpressionAnimation.SetScalarParameter("Sensitivity", 0.2f);
227+
//_skyViusalOffsetExpressionAnimation.SetScalarParameter("MaxDimension", SkyVisualRadius * 2 * SkyVisualAreaRatio);
228+
_skyVisual.StartAnimation("Offset", _skyViusalOffsetExpressionAnimation);
229+
}
230+
231+
private void CreateStarsVisuals()
232+
{
233+
for (int i = 0; i < NumberOfSmallTwinklingStars; i++)
234+
{
235+
CreateSmallTwinklingStarVisual();
236+
}
237+
238+
for (int i = 0; i < NumberOfBigMovingStars; i++)
239+
{
240+
CreateBigMovingStarVisual();
241+
}
242+
}
243+
244+
private void StartSkyCanvasStartupAnimations()
245+
{
246+
_skyVisual.StartScaleAnimation(new Vector2(0.8f, 0.8f), new Vector2(1.0f, 1.0f), 1600);
247+
_skyVisual.StartOpacityAnimation(0.0f, 1.0f, 1600);
248+
}
249+
250+
private void CreateSmallTwinklingStarVisual()
251+
{
252+
float z = _random.Create(-160, 160);
253+
float diameter = _random.Create(1, 3);
254+
var starVisual = CreateStarVisual(z, diameter);
255+
256+
double duration = _random.Create(40000, 80000);
257+
StartStarVisualOffsetAnimation(starVisual, duration);
258+
StartSmallTwinklingStarVisualOpacityAnimation(starVisual);
259+
260+
_skyVisual.Children.InsertAtTop(starVisual);
261+
}
262+
263+
private void CreateBigMovingStarVisual()
264+
{
265+
float offsetZ = _random.Create(-240, 0);
266+
float diameter = _random.Create(3, 5, (value) => value > 4);
267+
var starVisual = CreateStarVisual(offsetZ, diameter);
268+
269+
float finalOffsetZ = _random.Create(240, 400, (value) => value > 320);
270+
double duration = _random.Create(10000, 40000, (value) => value < 20000);
271+
StartStarVisualOffsetAnimation(starVisual, duration, finalOffsetZ);
272+
StartBigMovingStarVisualOpacityAnimation(starVisual);
273+
274+
_skyVisual.Children.InsertAtTop(starVisual);
275+
}
276+
277+
private SpriteVisual CreateStarVisual(float offsetZ, float diameter)
278+
{
279+
float offsetX = _random.Create(-SkyVisualRadius.ToInt(), SkyVisualRadius.ToInt());
280+
float offsetY = _random.Create(-SkyVisualRadius.ToInt(), SkyVisualRadius.ToInt());
281+
282+
var starVisual = _compositor.CreateSpriteVisual();
283+
starVisual.Opacity = 0.0f;
284+
starVisual.Brush = _circleBrush;
285+
starVisual.Offset = new Vector3(SkyVisualRadius + offsetX, SkyVisualRadius + offsetY, offsetZ);
286+
starVisual.Size = new Vector2(diameter, diameter);
287+
return starVisual;
288+
}
289+
290+
private void StartSmallTwinklingStarVisualOpacityAnimation(SpriteVisual starVisual)
291+
{
292+
float minOpacity = _random.Create(0, 60, (value) => value < 40);
293+
float maxOpacity = _random.Create(60, 100, (value) => value > 70);
294+
float duration = _random.Create(250, 500);
295+
296+
starVisual.StartOpacityAnimation(minOpacity / 100, maxOpacity / 100, duration,
297+
iterationBehavior: AnimationIterationBehavior.Forever);
298+
}
299+
300+
private void StartBigMovingStarVisualOpacityAnimation(SpriteVisual starVisual)
301+
{
302+
float maxOpacity = _random.Create(60, 100);
303+
float duration = _random.Create(500, 1000);
304+
305+
starVisual.StartOpacityAnimation(null, maxOpacity / 100, duration);
306+
}
307+
308+
private void StartStarVisualOffsetAnimation(SpriteVisual starVisual,
309+
double duration,
310+
float offsetZ = 0.0f)
311+
{
312+
if (_isUnloading || starVisual == null || _compositor == null)
313+
return;
314+
315+
float offsetX = _random.Create(-12, 12);
316+
float offsetY = _random.Create(-12, 12);
317+
var oldOffset = starVisual.Offset;
318+
319+
starVisual.StartOffsetAnimation(
320+
null,
321+
new Vector3(oldOffset.X + offsetX, oldOffset.Y + offsetY, oldOffset.Z + offsetZ),
322+
duration,
323+
completed: () =>
324+
{
325+
if (_isUnloading) return;
326+
StartStarVisualOffsetAnimation(starVisual, duration, offsetZ);
327+
});
328+
}
329+
}

0 commit comments

Comments
 (0)