NavigationPageCurvedHeaderPage.xaml.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. using System;
  2. using System.Threading.Tasks;
  3. using Avalonia;
  4. using Avalonia.Controls;
  5. using Avalonia.Controls.Shapes;
  6. using Avalonia.Input;
  7. using Avalonia.Interactivity;
  8. using Avalonia.Layout;
  9. using Avalonia.Media;
  10. using Avalonia.Media.Imaging;
  11. using Avalonia.Platform;
  12. using Avalonia.Styling;
  13. namespace ControlCatalog.Pages;
  14. public partial class NavigationPageCurvedHeaderPage : UserControl
  15. {
  16. static readonly Color Primary = Color.Parse("#137fec");
  17. static readonly Color BgLight = Color.Parse("#f6f7f8");
  18. static readonly Color TextDark = Color.Parse("#111827");
  19. static readonly Color TextMuted = Color.Parse("#64748b");
  20. const double DomeH = 32.0;
  21. const double HomeHeaderFlatH = 130.0;
  22. const double ProfileHeaderFlatH = 110.0;
  23. const double AvatarHomeSize = 72.0;
  24. const double AvatarProfileSize = 88.0;
  25. NavigationPage? _navPage;
  26. ScrollViewer? _infoPanel;
  27. public NavigationPageCurvedHeaderPage()
  28. {
  29. InitializeComponent();
  30. _navPage = this.FindControl<NavigationPage>("NavPage");
  31. if (_navPage != null)
  32. _ = _navPage.PushAsync(BuildHomePage());
  33. }
  34. protected override void OnLoaded(RoutedEventArgs e)
  35. {
  36. base.OnLoaded(e);
  37. _infoPanel = this.FindControl<ScrollViewer>("InfoPanel");
  38. UpdateInfoPanelVisibility();
  39. }
  40. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
  41. {
  42. base.OnPropertyChanged(change);
  43. if (change.Property == BoundsProperty)
  44. UpdateInfoPanelVisibility();
  45. }
  46. void UpdateInfoPanelVisibility()
  47. {
  48. if (_infoPanel != null)
  49. _infoPanel.IsVisible = Bounds.Width >= 650;
  50. }
  51. const string AssetBase = "avares://ControlCatalog/Assets/CurvedHeader/";
  52. static Bitmap? LoadImg(string name)
  53. {
  54. try { return new Bitmap(AssetLoader.Open(new Uri(AssetBase + name))); }
  55. catch { return null; }
  56. }
  57. static IBrush ImgBrush(string name, IBrush fallback)
  58. {
  59. var bmp = LoadImg(name);
  60. return bmp != null ? new ImageBrush(bmp) { Stretch = Stretch.UniformToFill } : fallback;
  61. }
  62. ContentPage BuildHomePage()
  63. {
  64. var bgPath = new Path { Fill = new SolidColorBrush(Colors.White) };
  65. var headerContent = new StackPanel
  66. {
  67. HorizontalAlignment = HorizontalAlignment.Center,
  68. Spacing = 4,
  69. Margin = new Thickness(24, 20, 24, 0),
  70. Children =
  71. {
  72. new TextBlock
  73. {
  74. Text = "Welcome back, Alex",
  75. FontSize = 20,
  76. FontWeight = FontWeight.Bold,
  77. Foreground = new SolidColorBrush(TextDark),
  78. TextAlignment = TextAlignment.Center,
  79. },
  80. new TextBlock
  81. {
  82. Text = "Ready to explore?",
  83. FontSize = 14,
  84. Foreground = new SolidColorBrush(TextMuted),
  85. TextAlignment = TextAlignment.Center,
  86. },
  87. new TextBox
  88. {
  89. Margin = new Thickness(0, 10, 0, 0),
  90. PlaceholderText = "Search for products...",
  91. CornerRadius = new CornerRadius(999),
  92. Height = 40,
  93. Padding = new Thickness(16, 10),
  94. FontSize = 14,
  95. TextAlignment = TextAlignment.Center,
  96. VerticalContentAlignment = VerticalAlignment.Center,
  97. Background = new SolidColorBrush(Color.Parse("#f1f5f9")),
  98. BorderThickness = new Thickness(0),
  99. Styles =
  100. {
  101. new Style(x => x.OfType<TextBox>().Template().OfType<TextBlock>().Name("PART_PlaceholderText"))
  102. {
  103. Setters =
  104. {
  105. new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Center),
  106. new Setter(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Stretch),
  107. }
  108. }
  109. }
  110. },
  111. }
  112. };
  113. var avatar = new Ellipse
  114. {
  115. Width = AvatarHomeSize,
  116. Height = AvatarHomeSize,
  117. Fill = ImgBrush("avatar.jpg", new SolidColorBrush(Color.Parse("#93c5fd"))),
  118. };
  119. var avatarRing = new Ellipse
  120. {
  121. Width = AvatarHomeSize + 6,
  122. Height = AvatarHomeSize + 6,
  123. Stroke = Brushes.White,
  124. StrokeThickness = 3,
  125. Fill = Brushes.Transparent,
  126. };
  127. var headerPanel = new Panel { VerticalAlignment = VerticalAlignment.Top };
  128. var homeScroll = new CurvedHeaderHomeScrollView
  129. {
  130. NavigateRequested = async () => { if (_navPage != null) await _navPage.PushAsync(BuildProfilePage()); },
  131. };
  132. headerPanel.SizeChanged += (_, args) =>
  133. {
  134. double w = args.NewSize.Width;
  135. if (w <= 1) return;
  136. bgPath.Data = BuildDomeGeometry(w, HomeHeaderFlatH, DomeH);
  137. double domeTipY = HomeHeaderFlatH + DomeH;
  138. Canvas.SetLeft(avatar, (w - AvatarHomeSize) / 2);
  139. Canvas.SetTop(avatar, domeTipY - AvatarHomeSize / 2);
  140. Canvas.SetLeft(avatarRing, (w - (AvatarHomeSize + 6)) / 2);
  141. Canvas.SetTop(avatarRing, domeTipY - (AvatarHomeSize + 6) / 2);
  142. };
  143. var avatarCanvas = new Canvas { IsHitTestVisible = true, Cursor = new Cursor(StandardCursorType.Hand) };
  144. avatarCanvas.Children.Add(avatarRing);
  145. avatarCanvas.Children.Add(avatar);
  146. avatarCanvas.PointerReleased += async (_, _) => { if (_navPage != null) await _navPage.PushAsync(BuildProfilePage()); };
  147. headerPanel.Children.Add(bgPath);
  148. headerPanel.Children.Add(headerContent);
  149. headerPanel.Children.Add(avatarCanvas);
  150. var root = new Panel();
  151. root.Children.Add(homeScroll);
  152. root.Children.Add(headerPanel);
  153. var page = new ContentPage
  154. {
  155. Background = new SolidColorBrush(BgLight),
  156. Content = root,
  157. };
  158. NavigationPage.SetHasNavigationBar(page, false);
  159. return page;
  160. }
  161. ContentPage BuildProfilePage()
  162. {
  163. var bgPath = new Path
  164. {
  165. Fill = new LinearGradientBrush
  166. {
  167. StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
  168. EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
  169. GradientStops =
  170. {
  171. new GradientStop(Color.Parse("#137fec"), 0),
  172. new GradientStop(Color.Parse("#0a4fa8"), 1),
  173. }
  174. }
  175. };
  176. var profileContent = new StackPanel
  177. {
  178. HorizontalAlignment = HorizontalAlignment.Center,
  179. Spacing = 3,
  180. Margin = new Thickness(24, 52, 24, 0),
  181. Children =
  182. {
  183. new TextBlock
  184. {
  185. Text = "Alex Johnson",
  186. FontSize = 20,
  187. FontWeight = FontWeight.Bold,
  188. Foreground = Brushes.White,
  189. TextAlignment = TextAlignment.Center,
  190. },
  191. new TextBlock
  192. {
  193. Text = "UI/UX Designer · San Francisco",
  194. FontSize = 13,
  195. Foreground = new SolidColorBrush(Color.FromArgb(210, 255, 255, 255)),
  196. TextAlignment = TextAlignment.Center,
  197. },
  198. }
  199. };
  200. var avatar = new Ellipse
  201. {
  202. Width = AvatarProfileSize,
  203. Height = AvatarProfileSize,
  204. Fill = ImgBrush("avatar.jpg", new SolidColorBrush(Color.Parse("#60a5fa"))),
  205. };
  206. var avatarRing = new Ellipse
  207. {
  208. Width = AvatarProfileSize + 6,
  209. Height = AvatarProfileSize + 6,
  210. Stroke = Brushes.White,
  211. StrokeThickness = 3,
  212. Fill = Brushes.Transparent,
  213. };
  214. var headerPanel = new Panel { VerticalAlignment = VerticalAlignment.Top };
  215. var profileScroll = new CurvedHeaderProfileScrollView();
  216. headerPanel.SizeChanged += (_, args) =>
  217. {
  218. double w = args.NewSize.Width;
  219. if (w <= 1) return;
  220. bgPath.Data = BuildDomeGeometry(w, ProfileHeaderFlatH, DomeH);
  221. double tipY = ProfileHeaderFlatH + DomeH;
  222. Canvas.SetLeft(avatar, (w - AvatarProfileSize) / 2);
  223. Canvas.SetTop(avatar, tipY - AvatarProfileSize / 2);
  224. Canvas.SetLeft(avatarRing, (w - (AvatarProfileSize + 6)) / 2);
  225. Canvas.SetTop(avatarRing, tipY - (AvatarProfileSize + 6) / 2);
  226. };
  227. var avatarCanvas = new Canvas { IsHitTestVisible = false };
  228. avatarCanvas.Children.Add(avatarRing);
  229. avatarCanvas.Children.Add(avatar);
  230. headerPanel.Children.Add(bgPath);
  231. headerPanel.Children.Add(profileContent);
  232. headerPanel.Children.Add(avatarCanvas);
  233. var root = new Panel();
  234. root.Children.Add(profileScroll);
  235. root.Children.Add(headerPanel);
  236. var page = new ContentPage
  237. {
  238. Background = new SolidColorBrush(BgLight),
  239. Content = root,
  240. };
  241. NavigationPage.SetBarLayoutBehavior(page, BarLayoutBehavior.Overlay);
  242. return page;
  243. }
  244. static StreamGeometry BuildDomeGeometry(double w, double flatH, double domeH)
  245. {
  246. var sg = new StreamGeometry();
  247. using var ctx = sg.Open();
  248. ctx.BeginFigure(new Point(0, 0), isFilled: true);
  249. ctx.LineTo(new Point(w, 0));
  250. ctx.LineTo(new Point(w, flatH));
  251. ctx.ArcTo(
  252. new Point(0, flatH),
  253. new Size(w / 2, domeH),
  254. rotationAngle: 0,
  255. isLargeArc: false,
  256. sweepDirection: SweepDirection.Clockwise);
  257. ctx.EndFigure(true);
  258. return sg;
  259. }
  260. }