16 private const int ScrollBarWidth = 12;
17 private const int ScrollBarMargin = 3;
18 private const int MinimumScrollBarThumbHeight = 18;
20 private SKColor _foregroundColor = SKColors.White;
21 private SKColor _backgroundColor = SKColors.Transparent;
25 private int _mouseWheelScrollPixels = 48;
26 private float _verticalScrollOffsetPx;
27 private float _verticalScrollEndPaddingPx;
28 private float _maximumVerticalScrollOffsetPx;
29 private bool _isDraggingScrollBarThumb;
30 private float _scrollBarDragOffset;
31 private int? _scrollBarPointerId;
45 string? nickname =
null)
48 ArgumentNullException.ThrowIfNull(view);
50 _text = text ??
string.Empty;
54 .
SetColors(_foregroundColor, _backgroundColor);
58 Add(
TextBlock, keepCurrentOffset:
false, explicitLocalOffsetPx: Vector2.Zero);
61 CompleteInitialization();
76 string? nickname =
null)
79 ArgumentNullException.ThrowIfNull(sceneLayer);
81 _text = text ??
string.Empty;
83 TextBlock =
new TextBlock(renderSurfaceHost, sceneLayer, view:
null, worldBounds: bounds, nickname: $
"{Nickname}.text")
85 .
SetColors(_foregroundColor, _backgroundColor);
89 Add(
TextBlock, keepCurrentOffset:
false, explicitLocalOffsetPx: Vector2.Zero);
92 CompleteInitialization();
119 return new Rectangle(
120 (
int)MathF.Round(position.X),
121 (
int)MathF.Round(position.Y),
137 RefreshScrollState();
149 get => _verticalScrollBarVisibility;
152 if (!Enum.IsDefined(value))
153 throw new ArgumentOutOfRangeException(nameof(value));
155 if (_verticalScrollBarVisibility == value)
158 _verticalScrollBarVisibility = value;
159 RefreshScrollState();
169 get => _mouseWheelScrollPixels;
170 set => _mouseWheelScrollPixels = value > 0
172 :
throw new ArgumentOutOfRangeException(nameof(value),
"Mouse-wheel scrolling must advance at least one pixel.");
185 get => _verticalScrollEndPaddingPx;
188 if (!
float.IsFinite(value) || value < 0f)
189 throw new ArgumentOutOfRangeException(nameof(value));
191 if (Math.Abs(_verticalScrollEndPaddingPx - value) < 0.001f)
194 _verticalScrollEndPaddingPx = value;
196 RefreshScrollState();
203 get => _verticalScrollOffsetPx;
206 if (!
float.IsFinite(value) || value < 0f)
207 throw new ArgumentOutOfRangeException(nameof(value));
209 SetVerticalScrollOffset(value);
223 _text = text ??
string.Empty;
225 RefreshScrollState();
238 ArgumentNullException.ThrowIfNull(typeface);
239 TextBlock.SetFont(typeface, size, minSize);
240 RefreshScrollState();
252 _foregroundColor = foreground;
253 _backgroundColor = background;
254 TextBlock.SetColors(_foregroundColor, _backgroundColor);
265 _foregroundColor = color;
266 TextBlock.SetColors(_foregroundColor, _backgroundColor);
277 _backgroundColor = color;
278 TextBlock.SetColors(_foregroundColor, _backgroundColor);
290 TextBlock.SetAlignment(horizontal, vertical);
302 RefreshScrollState();
314 if (!
float.IsFinite(horizontal) || horizontal < 0f)
315 throw new ArgumentOutOfRangeException(nameof(horizontal));
316 if (!
float.IsFinite(vertical) || vertical < 0f)
317 throw new ArgumentOutOfRangeException(nameof(vertical));
319 TextBlock.SetPadding(horizontal, vertical);
320 RefreshScrollState();
330 VerticalScrollBarTrack.ZOrder = zOrder + 1;
331 VerticalScrollBarThumb.ZOrder = zOrder + 2;
339 RefreshScrollState();
345 _isDraggingScrollBarThumb =
false;
346 _scrollBarPointerId =
null;
347 base.ProcessHidden();
353 base.OnPointerDown(args);
358 PointF local = GetLocalPointerPosition(args);
359 Rectangle trackBounds = GetScrollBarTrackBounds();
360 Rectangle thumbBounds = GetScrollBarThumbBounds();
362 if (!trackBounds.Contains(Point.Round(local)))
368 if (thumbBounds.Contains(Point.Round(local)))
370 _isDraggingScrollBarThumb =
true;
371 _scrollBarDragOffset = local.Y - thumbBounds.Top;
375 float page = Math.Max(1f, GetViewportContentHeight());
376 SetVerticalScrollOffset(_verticalScrollOffsetPx + (local.Y < thumbBounds.Top ? -page : page));
382 base.OnPointerMove(args);
384 if (!_isDraggingScrollBarThumb ||
391 Rectangle trackBounds = GetScrollBarTrackBounds();
392 Rectangle thumbBounds = GetScrollBarThumbBounds();
393 float travel = trackBounds.Height - thumbBounds.Height;
394 float maximumScroll = _maximumVerticalScrollOffsetPx;
396 if (travel <= 0f || maximumScroll <= 0f)
399 float desiredTop = GetLocalPointerPosition(args).Y - _scrollBarDragOffset;
400 float fraction = Math.Clamp((desiredTop - trackBounds.Top) / travel, 0f, 1f);
401 SetVerticalScrollOffset(fraction * maximumScroll);
408 base.OnPointerUp(args);
410 if (_scrollBarPointerId != args.
PointerId)
413 _isDraggingScrollBarThumb =
false;
414 _scrollBarPointerId =
null;
421 base.OnMouseWheel(args);
423 float maximumScroll = _maximumVerticalScrollOffsetPx;
424 if (args.
Delta == 0 || maximumScroll <= 0f)
427 long notches = Math.Max(1, Math.Abs((
long)args.
Delta) / 120);
429 SetVerticalScrollOffset(_verticalScrollOffsetPx + change);
433 private bool NeedsVerticalScrollBar =>
436 _maximumVerticalScrollOffsetPx > 0.5f);
438 private void CompleteInitialization()
440 VerticalScrollBarTrack.Visible =
false;
441 VerticalScrollBarThumb.Visible =
false;
443 RefreshScrollState();
446 private void RefreshScrollState()
450 Rectangle bounds =
Bounds;
451 SetTextBlockBounds(bounds);
453 if (!scrollingEnabled)
455 _verticalScrollOffsetPx = 0f;
463 _maximumVerticalScrollOffsetPx > 0.5f);
467 Rectangle contentBounds =
new(
470 Math.Max(1, bounds.Width - ScrollBarWidth - ScrollBarMargin * 2),
472 SetTextBlockBounds(contentBounds);
481 SetVerticalScrollOffset(_verticalScrollOffsetPx);
483 bool visible =
Visible && showScrollBar;
484 VerticalScrollBarTrack.Visible = visible;
485 VerticalScrollBarThumb.Visible = visible;
488 RefreshScrollBarBounds();
491 private void SetVerticalScrollOffset(
float value)
493 _verticalScrollOffsetPx = Math.Clamp(value, 0f, _maximumVerticalScrollOffsetPx);
494 TextBlock.VerticalScrollOffsetPx = _verticalScrollOffsetPx;
495 RefreshScrollBarBounds();
498 private float GetViewportContentHeight()
501 ? TextBlock.ScreenBounds
504 return Math.Max(0f, textBounds.Height -
TextBlock.VerticalPadding * 2f);
507 private void RefreshScrollBarBounds()
516 private Rectangle GetScrollBarTrackBounds()
519 Bounds.Right - ScrollBarMargin - ScrollBarWidth,
520 Bounds.Top + ScrollBarMargin,
522 Math.Max(1,
Bounds.Height - ScrollBarMargin * 2));
525 private Rectangle GetScrollBarThumbBounds()
527 Rectangle trackBounds = GetScrollBarTrackBounds();
528 float maximumScroll = _maximumVerticalScrollOffsetPx;
529 float viewportHeight = GetViewportContentHeight();
531 if (maximumScroll <= 0f || viewportHeight <= 0f)
534 float contentHeight = viewportHeight + maximumScroll;
535 int thumbHeight = Math.Clamp(
536 (
int)MathF.Round(trackBounds.Height * Math.Min(1f, viewportHeight / contentHeight)),
537 Math.Min(MinimumScrollBarThumbHeight, trackBounds.Height),
539 int travel = trackBounds.Height - thumbHeight;
540 int offset = maximumScroll <= 0f
542 : (int)MathF.Round(travel * (_verticalScrollOffsetPx / maximumScroll));
546 trackBounds.Top + offset,
551 private PointF GetLocalPointerPosition(WidgetPointerEventArgs args)
553 RectangleF screenBounds =
TextBlock.GetDrawLocationScreen(args.View);
555 args.WrappedOffsetWorldPx.X * args.View.Viewport.Zoom,
556 args.WrappedOffsetWorldPx.Y * args.View.Viewport.Zoom);
559 ? TextBlock.ScreenBounds
562 float scaleX = screenBounds.Width / textBounds.Width;
563 float scaleY = screenBounds.Height / textBounds.Height;
566 Bounds.Left + (args.ScreenPositionPx.X - screenBounds.Left) / scaleX,
567 Bounds.Top + (args.ScreenPositionPx.Y - screenBounds.Top) / scaleY);
570 private void SetTextBlockBounds(Rectangle bounds)
574 TextBlock.SetPosition(
new Vector2(bounds.X, bounds.Y));
579 new Vector2(bounds.X - overallBounds.X, bounds.Y - overallBounds.Y));
582 private void SetRectangleBounds(DirectRectangle rectangle, Rectangle bounds)
585 rectangle.ScreenBounds = bounds;
587 rectangle.WorldBounds = bounds;
594 private static DirectRectangle CreateScrollBarTrack(
595 RenderSurfaceHostBase host,
598 => ConfigureScrollBarTrack(
new DirectRectangle(Color.FromArgb(255, 26, 26, 32), host, view, bounds));
600 private static DirectRectangle CreateScrollBarTrack(
601 RenderSurfaceHostBase host,
604 => ConfigureScrollBarTrack(
new DirectRectangle(Color.FromArgb(255, 26, 26, 32), host, sceneLayer, bounds));
606 private static DirectRectangle ConfigureScrollBarTrack(DirectRectangle track)
607 => track.SetFilled(
true).SetStrokeWidth(0f).SetCornerRadius(3f);
609 private static DirectRectangle CreateScrollBarThumb(
610 RenderSurfaceHostBase host,
613 => ConfigureScrollBarThumb(
new DirectRectangle(Color.FromArgb(255, 126, 126, 142), host, view, bounds));
615 private static DirectRectangle CreateScrollBarThumb(
616 RenderSurfaceHostBase host,
619 => ConfigureScrollBarThumb(
new DirectRectangle(Color.FromArgb(255, 126, 126, 142), host, sceneLayer, bounds));
621 private static DirectRectangle ConfigureScrollBarThumb(DirectRectangle thumb)
622 => thumb.SetFilled(
true).SetStrokeWidth(0f).SetCornerRadius(3f);
624 private static Rectangle ValidateBounds(Rectangle bounds)
626 ValidateSize(bounds.Size);
630 private static void ValidateSize(
Size size)
632 if (size.Width <= 0 || size.Height <= 0)
633 throw new ArgumentOutOfRangeException(nameof(size), size,
"Label size must be positive.");
A retained-mode text drawable with wrapping, multi-line ( ) support, horizontal/vertical alignment,...
TextBlock SetColors(SKColor fg, SKColor bg)
Sets the foreground (text) and background colors using Skia colors.
TextBlock SetText(string text)
Sets the current text content and rebuilds layout as needed. If no reveal animation is active,...