UI Components
The Popcorning Guinea Pig engine provides a comprehensive set of UI components for building interactive user interfaces, all built on a hierarchical system with flexible styling and positioning.
Overview
The UI system features:
- Hierarchical component tree with parent-child relationships
- Flexible positioning and sizing options
- Event-driven interaction model
- Consistent styling and theming support
- Built-in accessibility features
- Custom component creation capabilities
Base UI Component
All UI components inherit from the base UIElement class:
public abstract class UIElement
{
public Vector2 Position { get; set; }
public Vector2 Size { get; set; }
public AnchorPoint Anchor { get; set; }
public bool Visible { get; set; }
public bool Enabled { get; set; }
public float Alpha { get; set; }
public Color Tint { get; set; }
public string Name { get; set; }
// Hierarchy
public UIElement Parent { get; protected set; }
public List<UIElement> Children { get; protected set; }
// Events
public event Action<UIElement> OnClick;
public event Action<UIElement> OnHover;
public event Action<UIElement> OnFocus;
// Methods
public virtual void AddChild(UIElement child);
public virtual void RemoveChild(UIElement child);
public virtual void Update(GameTime gameTime);
public virtual void Draw(SpriteBatch spriteBatch);
}
Core UI Components
Label
Display text with various formatting options:
public class Label : UIElement
{
public string Text { get; set; }
public Font Font { get; set; }
public Color TextColor { get; set; }
public TextAlignment Alignment { get; set; }
public bool WordWrap { get; set; }
public float LineSpacing { get; set; }
}
// Usage
var titleLabel = new Label
{
Text = "Game Title",
Position = new Vector2(400, 100),
Font = assetRegistry.LoadAsset<Font>("fonts/title.ttf"),
TextColor = Color.White,
Alignment = TextAlignment.Center
};
Button
Interactive button component with multiple states:
public class Button : UIElement
{
public string Text { get; set; }
public Font Font { get; set; }
public Texture2D NormalTexture { get; set; }
public Texture2D HoverTexture { get; set; }
public Texture2D PressedTexture { get; set; }
public Texture2D DisabledTexture { get; set; }
public Color TextColor { get; set; }
public ButtonState State { get; protected set; }
public event Action OnClick;
public event Action OnPressed;
public event Action OnReleased;
}
// Usage
var playButton = new Button
{
Text = "Play Game",
Position = new Vector2(400, 200),
Size = new Vector2(200, 60),
NormalTexture = assetRegistry.LoadAsset<Texture2D>("ui/button_normal.png"),
HoverTexture = assetRegistry.LoadAsset<Texture2D>("ui/button_hover.png"),
PressedTexture = assetRegistry.LoadAsset<Texture2D>("ui/button_pressed.png"),
OnClick = () => StartGame()
};
Panel
Container component for organizing other UI elements:
public class Panel : UIElement
{
public Texture2D BackgroundTexture { get; set; }
public Color BackgroundColor { get; set; }
public Texture2D BorderTexture { get; set; }
public float BorderThickness { get; set; }
public bool ClipChildren { get; set; }
public Padding Padding { get; set; }
}
// Usage
var settingsPanel = new Panel
{
Position = new Vector2(50, 50),
Size = new Vector2(400, 500),
BackgroundColor = new Color(0, 0, 0, 180), // Semi-transparent black
BorderThickness = 2,
Padding = new Padding(20)
};
Slider
Interactive slider for numeric value selection:
public class Slider : UIElement
{
public float MinValue { get; set; }
public float MaxValue { get; set; }
public float Value { get; set; }
public float StepSize { get; set; }
public Texture2D TrackTexture { get; set; }
public Texture2D HandleTexture { get; set; }
public bool ShowValue { get; set; }
public event Action<float> OnValueChanged;
}
// Usage
var volumeSlider = new Slider
{
Position = new Vector2(200, 300),
Size = new Vector2(200, 20),
MinValue = 0f,
MaxValue = 1f,
Value = 0.8f,
StepSize = 0.01f,
OnValueChanged = (value) => SetMasterVolume(value)
};
Toggle
Checkbox or toggle switch component:
public class Toggle : UIElement
{
public bool IsChecked { get; set; }
public string Text { get; set; }
public Texture2D CheckedTexture { get; set; }
public Texture2D UncheckedTexture { get; set; }
public Texture2D HoverTexture { get; set; }
public Font Font { get; set; }
public event Action<bool> OnToggled;
}
// Usage
var fullscreenToggle = new Toggle
{
Text = "Fullscreen",
Position = new Vector2(200, 350),
IsChecked = true,
OnToggled = (isChecked) => SetFullscreen(isChecked)
};
Dropdown
Dropdown menu for selecting from multiple options:
public class Dropdown : UIElement
{
public string[] Items { get; set; }
public int SelectedIndex { get; set; }
public string SelectedItem => SelectedIndex >= 0 && SelectedIndex < Items.Length ? Items[SelectedIndex] : null;
public bool IsOpen { get; protected set; }
public Font Font { get; set; }
public Texture2D BackgroundTexture { get; set; }
public Texture2D ArrowTexture { get; set; }
public event Action<int> OnSelectionChanged;
}
// Usage
var resolutionDropdown = new Dropdown
{
Items = new[] { "1920x1080", "1600x900", "1280x720" },
SelectedIndex = 0,
Position = new Vector2(200, 400),
Size = new Vector2(150, 30),
OnSelectionChanged = (index) => SetResolution(index)
};
Advanced Components
GridContainer
Layout container that arranges children in a grid:
public class GridContainer : UIElement
{
public int Rows { get; set; }
public int Columns { get; set; }
public Vector2 CellSize { get; set; }
public Vector2 Spacing { get; set; }
public GridAlignment Alignment { get; set; }
public void AddChild(UIElement child, int row, int column);
public void RemoveChild(int row, int column);
public UIElement GetChild(int row, int column);
}
// Usage
var inventoryGrid = new GridContainer
{
Rows = 5,
Columns = 8,
CellSize = new Vector2(50, 50),
Spacing = new Vector2(5, 5),
Position = new Vector2(100, 100)
};
// Add items to specific grid positions
for (int row = 0; row < 5; row++)
{
for (int col = 0; col < 8; col++)
{
var slot = new ItemSlot();
inventoryGrid.AddChild(slot, row, col);
}
}
ScrollView
Scrollable container for content larger than the display area:
public class ScrollView : UIElement
{
public Vector2 ContentSize { get; set; }
public Vector2 ScrollOffset { get; set; }
public bool ShowScrollbars { get; set; }
public ScrollbarStyle ScrollbarStyle { get; set; }
public UIElement Content { get; set; }
public void ScrollTo(Vector2 position);
public void ScrollToChild(UIElement child);
}
// Usage
var chatScrollView = new ScrollView
{
Position = new Vector2(50, 50),
Size = new Vector2(300, 400),
ShowScrollbars = true,
ContentSize = new Vector2(300, 800) // Content is taller than view
};
var chatContent = new Panel
{
Size = new Vector2(300, 800)
};
chatScrollView.Content = chatContent;
ProgressBar
Visual progress indicator:
public class ProgressBar : UIElement
{
public float MinValue { get; set; }
public float MaxValue { get; set; }
public float Value { get; set; }
public ProgressBarStyle Style { get; set; }
public Color FillColor { get; set; }
public Color BackgroundColor { get; set; }
public bool ShowPercentage { get; set; }
public Font Font { get; set; }
}
// Usage
var healthBar = new ProgressBar
{
Position = new Vector2(50, 50),
Size = new Vector2(200, 20),
MinValue = 0,
MaxValue = 100,
Value = 75,
FillColor = Color.Red,
BackgroundColor = Color.DarkRed,
ShowPercentage = true
};
InputField
Text input component with validation:
public class InputField : UIElement
{
public string Text { get; set; }
public string PlaceholderText { get; set; }
public Font Font { get; set; }
public int MaxLength { get; set; }
public bool IsPassword { get; set; }
public bool IsReadOnly { get; set; }
public TextValidation Validation { get; set; }
public bool HasFocus { get; protected set; }
public event Action<string> OnTextChanged;
public event Action OnEnterPressed;
}
// Usage
var playerNameInput = new InputField
{
Position = new Vector2(200, 250),
Size = new Vector2(200, 30),
PlaceholderText = "Enter your name",
MaxLength = 20,
Validation = TextValidation.AlphaNumeric,
OnTextChanged = (text) => ValidatePlayerName(text)
};
Component Styling
Styling System
Apply consistent styling across components:
public class UIStyle
{
public Color BackgroundColor { get; set; }
public Color TextColor { get; set; }
public Color BorderColor { get; set; }
public float BorderThickness { get; set; }
public Font Font { get; set; }
public Padding Padding { get; set; }
public Padding Margin { get; set; }
}
public class UITheme
{
public UIStyle DefaultStyle { get; set; }
public UIStyle ButtonStyle { get; set; }
public UIStyle PanelStyle { get; set; }
public UIStyle LabelStyle { get; set; }
public void ApplyTheme(UIElement element);
}
// Usage
var darkTheme = new UITheme
{
DefaultStyle = new UIStyle
{
BackgroundColor = new Color(32, 32, 32),
TextColor = Color.White,
BorderColor = Color.Gray,
Font = defaultFont
},
ButtonStyle = new UIStyle
{
BackgroundColor = new Color(64, 64, 64),
TextColor = Color.White,
BorderThickness = 2
}
};
// Apply theme to components
darkTheme.ApplyTheme(myButton);
darkTheme.ApplyTheme(myPanel);
Custom Styling
Create custom styled components:
public class StyledButton : Button
{
public UIStyle CustomStyle { get; set; }
public override void Draw(SpriteBatch spriteBatch)
{
if (CustomStyle != null)
{
ApplyStyle(CustomStyle);
}
base.Draw(spriteBatch);
}
private void ApplyStyle(UIStyle style)
{
TextColor = style.TextColor;
if (style.Font != null)
Font = style.Font;
// Apply other style properties...
}
}
Event Handling
Component Events
Handle user interactions with events:
public class MenuScene : Scene
{
private Button _playButton;
private Button _settingsButton;
private Slider _volumeSlider;
public MenuScene()
{
SetupUI();
}
private void SetupUI()
{
_playButton = new Button
{
Text = "Play",
Position = new Vector2(400, 200),
OnClick = OnPlayClicked,
OnHover = OnButtonHovered
};
_settingsButton = new Button
{
Text = "Settings",
Position = new Vector2(400, 250),
OnClick = OnSettingsClicked
};
_volumeSlider = new Slider
{
Position = new Vector2(300, 300),
MinValue = 0f,
MaxValue = 1f,
OnValueChanged = OnVolumeChanged
};
AddElement(_playButton);
AddElement(_settingsButton);
AddElement(_volumeSlider);
}
private void OnPlayClicked()
{
SceneManager.LoadScene("Gameplay");
}
private void OnSettingsClicked()
{
SceneManager.PushScene("Settings");
}
private void OnVolumeChanged(float volume)
{
AudioManager.SetMasterVolume(volume);
}
private void OnButtonHovered()
{
AudioManager.PlaySound("ui/hover.wav");
}
}
Event Propagation
Control how events bubble through the UI hierarchy:
public class UIEventSystem
{
public void ProcessEvents(UIElement root, InputState input)
{
var hitElement = FindHitElement(root, input.MousePosition);
if (input.IsMouseButtonPressed(MouseButton.Left))
{
ProcessClickEvent(hitElement, input);
}
if (input.MousePosition != _lastMousePosition)
{
ProcessHoverEvent(hitElement, input);
}
}
private void ProcessClickEvent(UIElement element, InputState input)
{
var currentElement = element;
while (currentElement != null)
{
if (currentElement.OnClick != null)
{
currentElement.OnClick.Invoke(currentElement);
// Stop propagation if event is handled
if (currentElement.ConsumesEvents)
break;
}
currentElement = currentElement.Parent;
}
}
}
Animation
Component Animation
Animate UI components for better user experience:
public class UIAnimator
{
private readonly List<UIAnimation> _activeAnimations;
public void AnimatePosition(UIElement element, Vector2 targetPosition, float duration)
{
var animation = new PositionAnimation
{
Element = element,
StartPosition = element.Position,
TargetPosition = targetPosition,
Duration = duration,
EasingFunction = EasingFunctions.EaseOutQuad
};
_activeAnimations.Add(animation);
}
public void AnimateScale(UIElement element, Vector2 targetScale, float duration)
{
var animation = new ScaleAnimation
{
Element = element,
StartScale = element.Size,
TargetScale = targetScale,
Duration = duration
};
_activeAnimations.Add(animation);
}
public void AnimateAlpha(UIElement element, float targetAlpha, float duration)
{
var animation = new AlphaAnimation
{
Element = element,
StartAlpha = element.Alpha,
TargetAlpha = targetAlpha,
Duration = duration
};
_activeAnimations.Add(animation);
}
}
// Usage
var animator = new UIAnimator();
// Fade in a panel
animator.AnimateAlpha(myPanel, 1.0f, 0.5f);
// Slide in a menu
animator.AnimatePosition(mainMenu, new Vector2(0, 0), 0.3f);
Transition Effects
Create smooth transitions between UI states:
public class UITransitions
{
public static void SlideIn(UIElement element, Direction direction, float duration = 0.3f)
{
var startPos = GetOffscreenPosition(element, direction);
var endPos = element.Position;
element.Position = startPos;
var tween = new Tween(0f, 1f, duration);
tween.OnUpdate = (progress) =>
{
element.Position = Vector2.Lerp(startPos, endPos, progress);
};
TweenManager.AddTween(tween);
}
public static void FadeIn(UIElement element, float duration = 0.3f)
{
element.Alpha = 0f;
var tween = new Tween(0f, 1f, duration);
tween.OnUpdate = (progress) =>
{
element.Alpha = progress;
};
TweenManager.AddTween(tween);
}
}
Custom Components
Creating Custom Components
Extend the base UIElement to create specialized components:
public class HealthBar : UIElement
{
private float _currentHealth;
private float _maxHealth;
private Color _healthColor;
private Color _backgroundColor;
public float CurrentHealth
{
get => _currentHealth;
set
{
_currentHealth = Math.Clamp(value, 0, _maxHealth);
OnHealthChanged?.Invoke(_currentHealth);
}
}
public float MaxHealth
{
get => _maxHealth;
set => _maxHealth = Math.Max(0, value);
}
public event Action<float> OnHealthChanged;
public override void Draw(SpriteBatch spriteBatch)
{
var bounds = GetAbsoluteBounds();
// Draw background
spriteBatch.FillRectangle(bounds, _backgroundColor);
// Draw health fill
var healthPercentage = _maxHealth > 0 ? _currentHealth / _maxHealth : 0;
var fillWidth = bounds.Width * healthPercentage;
var fillBounds = new Rectangle(bounds.X, bounds.Y, (int)fillWidth, bounds.Height);
spriteBatch.FillRectangle(fillBounds, _healthColor);
base.Draw(spriteBatch);
}
}
// Usage
var playerHealthBar = new HealthBar
{
Position = new Vector2(20, 20),
Size = new Vector2(200, 20),
MaxHealth = 100,
CurrentHealth = 80,
OnHealthChanged = (health) =>
{
if (health <= 0)
ShowGameOverScreen();
}
};
Component Composition
Combine multiple components to create complex UI elements:
public class PlayerInfoPanel : Panel
{
private Label _nameLabel;
private HealthBar _healthBar;
private ProgressBar _experienceBar;
private Label _levelLabel;
public string PlayerName
{
get => _nameLabel.Text;
set => _nameLabel.Text = value;
}
public float Health
{
get => _healthBar.CurrentHealth;
set => _healthBar.CurrentHealth = value;
}
public int Level { get; set; }
public PlayerInfoPanel()
{
Size = new Vector2(300, 120);
BackgroundColor = new Color(0, 0, 0, 128);
CreateChildComponents();
LayoutComponents();
}
private void CreateChildComponents()
{
_nameLabel = new Label
{
Text = "Player",
TextColor = Color.White,
Font = GameFonts.DefaultFont
};
_healthBar = new HealthBar
{
MaxHealth = 100,
CurrentHealth = 100
};
_experienceBar = new ProgressBar
{
MaxValue = 1000,
FillColor = Color.Yellow
};
_levelLabel = new Label
{
Text = "Level 1",
TextColor = Color.Gold,
Font = GameFonts.SmallFont
};
AddChild(_nameLabel);
AddChild(_healthBar);
AddChild(_experienceBar);
AddChild(_levelLabel);
}
private void LayoutComponents()
{
_nameLabel.Position = new Vector2(10, 10);
_levelLabel.Position = new Vector2(200, 10);
_healthBar.Position = new Vector2(10, 40);
_healthBar.Size = new Vector2(280, 20);
_experienceBar.Position = new Vector2(10, 70);
_experienceBar.Size = new Vector2(280, 15);
}
}
Best Practices
Performance
- Object pooling: Reuse UI components to reduce garbage collection
- Visibility culling: Don't update or draw invisible components
- Batch rendering: Group similar UI elements for efficient rendering
- Minimize hierarchy depth: Keep UI trees reasonably shallow
Accessibility
- Keyboard navigation: Support tab order and keyboard shortcuts
- Screen reader support: Provide text alternatives for visual elements
- High contrast: Support high contrast themes
- Scalable text: Allow text size adjustments
Design
- Consistent spacing: Use consistent margins and padding
- Visual hierarchy: Use size, color, and position to show importance
- Feedback: Provide visual and audio feedback for interactions
- Responsive layout: Support different screen sizes and resolutions
The UI component system provides a flexible foundation for creating rich, interactive user interfaces in your Popcorning Guinea Pig games.