Skip to content

XAML Views

hifihedgehog edited this page Mar 16, 2026 · 36 revisions

XAML Views Reference

All views live in PadForge.App/Views/ under the PadForge.Views namespace. The app uses ModernWpfUI for Windows 10/11 Fluent Design styling.


Application Shell (MainWindow)

Files: MainWindow.xaml, MainWindow.xaml.cs

The application shell. Contains the NavigationView sidebar, page content area, status bar, and driver overlay.

XAML Structure

Two-row Grid:

  • Row 0 (star): NavigationView containing all page containers.
  • Row 1 (auto): Status bar Border.
  • Overlay (ZIndex=1000): Driver operation overlay (blocks UI during install/uninstall).
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>    <!-- NavigationView -->
        <RowDefinition Height="Auto"/> <!-- Status bar -->
    </Grid.RowDefinitions>

    <ui:NavigationView x:Name="NavView" PaneDisplayMode="Left" PaneTitle="PadForge" OpenPaneLength="207"
                       IsBackButtonVisible="Collapsed" IsSettingsVisible="True">
        <Grid>
            <views:DashboardPage x:Name="DashboardPageView" Visibility="Visible"/>
            <views:PadPage x:Name="PadPageView" Visibility="Collapsed"/>
            <views:DevicesPage x:Name="DevicesPageView" Visibility="Collapsed"/>
            <views:SettingsPage x:Name="SettingsPageView" Visibility="Collapsed"/>
            <views:ProfilesPage x:Name="ProfilesPageView" Visibility="Collapsed"/>
            <views:AboutPage x:Name="AboutPageView" Visibility="Collapsed"/>
        </Grid>
    </ui:NavigationView>

    <Border Grid.Row="1"> <!-- Status bar --> </Border>
    <Grid x:Name="DriverOverlay" Grid.RowSpan="2" Panel.ZIndex="1000"> <!-- ... --> </Grid>
</Grid>

Navigation Model

PadForge does not use WPF Frame-based navigation. All pages are instantiated once in the XAML Grid and visibility-swapped:

  1. NavView_SelectionChanged reads the selected item's Tag string.
  2. All page containers set to Visibility.Collapsed.
  3. The matching page set to Visibility.Visible.
  4. For controller slots (tag "Pad:{index}"), the PadPage's DataContext is set to the corresponding PadViewModel.

This approach preserves control state (scroll position, selected tabs, text field contents) across navigation since pages are never destroyed.

Sidebar Construction

The sidebar is built programmatically in BuildNavigationItems(). Fixed items:

Tag Content Icon
Dashboard Dashboard Home SymbolIcon
Devices Devices Gamepad SymbolIcon
Profiles Profiles People SymbolIcon
About About Help SymbolIcon (footer)
Settings (built-in) Built-in gear icon

Dynamic controller cards are inserted between "Devices" and "Profiles" via RebuildControllerSection(). Each card is a NavigationViewItem with custom Content containing:

  • Power/type glyph (Xbox/DS4/vJoy icon)
  • Slot label ("Controller 1", etc.)
  • Device name subtitle
  • Delete button (visible on hover)

RebuildControllerSection() is called when slots are created, deleted, or reordered. It uses a _rebuildingControllerSection guard to prevent re-entrancy during NavigationView selection changes.

Sidebar Drag Reordering

Users can drag controller cards to reorder virtual controller slots:

  1. OnCardDragStartPreviewMouseLeftButtonDown records start position.
  2. OnNavViewDragMovePreviewMouseMove checks distance threshold, then BeginCardDrag() creates a CardDragAdorner (ghost preview) and InsertionLineAdorner (drop indicator line).
  3. UpdateDragPosition — Updates adorner positions, computes target insertion index.
  4. EndCardDragPreviewMouseLeftButtonUp completes the swap. Calls SettingsManager.SwapSlots() and EnsureTypeGroupOrder().

Cross-Panel Device Drag-Drop

Devices can be dragged from the Devices page card list to a sidebar controller card:

  • DevicesPage initiates DragDrop.DoDragDrop() with DeviceRowViewModel as the data payload.
  • Sidebar NavigationViewItem handlers (DragOver, Drop) accept the drop and assign the device to that slot.

Add Controller Popup

A Popup with buttons for Xbox 360, DualShock 4, vJoy, MIDI, and Keyboard+Mouse. Per-type buttons disabled at capacity:

  • Opacity 0.35 and "(max N)" tooltip when limit reached.
  • HasAnyControllerTypeCapacity() counts per-type from Pads[].OutputType.
  • All types share the global limit: MaxXbox360Slots = MaxDS4Slots = MaxVJoySlots = MaxMidiSlots = MaxKeyboardMouseSlots = 16.

Status Bar

Bottom Border with four columns:

  1. Status textStatusText binding, trimmed with CharacterEllipsis.
  2. Device countConnectedDeviceCount with "device(s)" suffix.
  3. Polling frequencyPollingFrequency formatted as "{0:F0} Hz".
  4. Engine indicator — Green/gray dot (BoolToColorConverter) + EngineStatusText.

Driver Overlay

Semi-transparent overlay shown during driver install/uninstall operations:

  • ProgressRing spinner + text message.
  • Blocks all UI interaction (ZIndex=1000, Grid.RowSpan="2").
  • Shown/hidden by RunDriverOperationAsync().

Composition Root (Code-Behind)

MainWindow.xaml.cs is the service wiring hub (~2600 lines). Constructor:

  1. Creates MainViewModel as root ViewModel; sets DataContext.
  2. Sets child DataContext: Dashboard, Devices, Settings, Profiles pages.
  3. Creates services: SettingsService, InputService, RecorderService, DeviceService.
  4. Wires all ViewModel events to services:
    • StartEngineRequested/StopEngineRequested to InputService.Start()/Stop().
    • SaveRequested/ReloadRequested/ResetRequested to SettingsService.
    • Driver install/uninstall commands to DriverInstaller methods wrapped in RunDriverOperationAsync.
    • TestRumbleRequested/TestLeftMotorRequested/TestRightMotorRequested per pad.
    • Recording flow events per pad/mapping row.
    • Profile management events (New, SaveAs, Edit, Load, Delete, RevertToDefault).
    • Device assignment events via DeviceService.

Timer Architecture

Timer Interval Purpose
DispatcherTimer 33ms (~30Hz) Calls InputService.UpdateUI() to push engine state into ViewModels
_driverStatusTimer 5s Polls ViGEm/vJoy driver status for hot-plug detection
CompositionTarget.Rendering ~60fps Used by 3D/2D/Schematic views for per-frame visual updates

DashboardPage

Files: DashboardPage.xaml, DashboardPage.xaml.cs

Overview page with engine toggle, slot summary cards, DSU settings, and driver status.

Layout

  • Engine toggle: Start/Stop button with status indicator dot.
  • Slot cards: WrapPanel inside ItemsControl bound to DashboardViewModel.SlotSummaries. Each card shows slot label, device name, status dot (green=active, gray=idle), and output type icon with instance number.
  • "Add Controller" card: Shown when ShowAddController is true. Opens the type selection popup.
  • DSU settings: Enable toggle, port number field, server status text.
  • Driver status: Three rows showing ViGEmBus, HidHide, and vJoy installation status with green/gray indicator dots.
  • Card drag reordering: Slot cards support drag to reorder (same adorner system as sidebar).

PadPage

Files: PadPage.xaml, PadPage.xaml.cs

Configuration page for a single virtual controller slot. Contains a custom tab strip and 6 tab panels.

Custom Tab Strip

Horizontal bar of RadioButton controls styled via a custom ControlTemplate (the TabStripButton style). Each RadioButton:

  • Uses GroupName="PadTab" for mutual exclusion.
  • Stores the tab index in its Tag property.
  • Click handler calls vm.SelectedConfigTab = idx.

A hidden TabControl header via custom ControlTemplate provides the actual content switching. The TabControl's SelectedIndex is bound to SelectedConfigTab.

Tab Panels

Index Tab Name Description
0 Controller 3D/2D/Schematic/KBM model visualization
1 Macros Macro editor with trigger and action configuration (combo triggers, mouse actions)
2 Mappings DataGrid of MappingItem rows with Record/Clear buttons
3 Sticks Dynamic ItemsControl of StickConfigItem with dead zone shape, sensitivity curves, and live preview
4 Triggers Dynamic ItemsControl of TriggerConfigItem with sensitivity curve and RangeSlider for dead zone/max range
5 Force Feedback Motor strength sliders, swap toggle, gain control, test buttons

Macros Tab Details

The Macros tab trigger section uses a two-tier layout:

  1. Fire mode dropdown: Selects MacroTriggerMode (OnPress, OnRelease, WhileHeld, Always). When Always is selected, the Trigger Combination panel is hidden and a note explains the macro fires every frame.
  2. Trigger Combination panel (conditional, hidden in Always mode): Contains the Record button for button combos, plus:
    • Axis threshold slider: Bound to TriggerAxisThreshold (1-100%). Visible when UsesAxisTrigger is true.
    • POV trigger indicators: Shows recorded POV directions from TriggerPovs.

The action list supports mouse action types (MouseMove, MouseButtonPress, MouseButtonRelease, MouseScroll) with sensitivity and mouse button dropdowns.

Sticks Tab Details

Each stick section includes, in order:

  • Dead Zone Shape ComboBox (ScaledRadial, Radial, Axial, Hybrid, SlopedScaledAxial, SlopedAxial)
  • Center Offset sliders for X and Y with Calibrate Center button
  • Dead Zone X / Y sliders (0-100%)
  • Anti-Dead Zone X / Y sliders (0-100%)
  • Linear slider (0-100%)
  • Sensitivity X / Y ComboBox (preset dropdown) + CurveEditor per axis
  • Max Range X / Y sliders (1-100%)
  • Per-row reset buttons and a Reset All button at the top

Triggers Tab Details

Each trigger section includes:

  • Dead Zone / Max Range via RangeSlider (combined visualization)
  • Anti-Dead Zone slider (0-100%)
  • Sensitivity Curve ComboBox (preset dropdown) + CurveEditor
  • Per-row reset buttons and a Reset All button at the top

Controller Tab (View Switching)

Hosts one of four views depending on output type and user settings:

  1. ControllerModelView (3D, HelixToolkit) — for Xbox 360/DS4 when Use2DControllerView is false.
  2. ControllerModel2DView (2D overlays) — for Xbox 360/DS4 when Use2DControllerView is true.
  3. ControllerSchematicView (procedural) — for custom vJoy (non-gamepad preset), always.
  4. KBMPreviewView — for Keyboard+Mouse output type, always (see below). MappingLabel() tooltip helper maps element names to human-readable labels. X1/X2 side button Rectangle elements are promoted to named fields for flash highlight support.
  5. MidiPreviewView — for MIDI output type, always.

ApplyViewMode() in PadPage.xaml.cs controls which view is visible:

  • Custom vJoy: Schematic always, 2D/3D toggle hidden.
  • Gamepad: 2D/3D toggle visible, user preference from SettingsViewModel.Use2DControllerView.

BindActiveModelView() wires the active view:

  1. Unbinds all three views.
  2. Subscribes the active view's ControllerElementRecordRequested event.
  3. Calls Bind(vm) on the active view.

All three views fire ControllerElementRecordRequested with a PadSetting target name string for click-to-record.

View Mode Toggle

Button with icon that switches between 2D and 3D:

  • E8B9 (flat/photo icon) shown in 3D mode, click switches to 2D.
  • F158 (3D/cube icon) shown in 2D mode, click switches to 3D.
  • Hidden when displaying custom vJoy schematic.

vJoy Config Bar

Visible when OutputType == VJoy. Contains:

  • Preset ComboBox (Xbox 360, DualShock 4, Custom).
  • Four spinners visible only in Custom: ThumbstickCount, TriggerCount, PovCount, ButtonCount.
  • VJoyPresetCombo_SelectionChanged updates VJoyConfig.Preset and calls SyncVJoyCustomFields().
  • _syncingVJoyConfig guard prevents recursive updates during programmatic sync.

MIDI Config Bar

Visible when OutputType == Midi. Contains:

  • Channel ComboBox (1-16).
  • CC Count and Start CC spinners (interdependent clamping to 0-127 range).
  • Note Count and Start Note spinners (interdependent clamping to 0-127 range).
  • Velocity spinner (0-127).

All fields bind to PadViewModel.MidiConfig (MidiSlotConfig) properties.

Multi-Device Selector

ComboBox bound to MappedDevices with SelectedMappedDevice. Visible when multiple physical devices are assigned to one slot.

Copy From Dialog

"Copy From" button opens CopyFromDialog to copy mappings from another slot's device.


DevicesPage

Files: DevicesPage.xaml, DevicesPage.xaml.cs

Lists all detected input devices with raw input state visualization.

Device List (Left Panel)

ListBox with card ItemTemplate. Each card shows:

  • Status dot (green=online, gray=offline via BoolToColorConverter)
  • Device name (bold)
  • Slot badges (SlotBadges collection with sequential numbering)
  • Device type and capabilities summary
  • VID/PID

Selection highlighting: Custom ControlTemplate for ListBoxItem with 4px accent-colored left bar on the selected card.

Detail Panel (Right Panel)

Shown when a device is selected (HasSelectedDevice). Contains:

  • Axes: ItemsControl of ProgressBar elements (0-100% range via AxisToPercentConverter). Each bar shows name, normalized bar, and raw value.
  • Buttons: WrapPanel of circles. Accent fill when pressed (BoolToOpacityConverter). Button total label removed from display. For keyboard devices, displays KeyboardKeys on a positioned Canvas inside a Viewbox (QWERTY layout). For mouse devices, uses MousePreviewControl for graphical preview.
  • POV hats: ItemsControl bound to RawPovs. Each POV displays as a compass with a RotateTransform arrow (PovToAngleConverter converts centidegrees to degrees).
  • Gyroscope/Accelerometer: Grid showing X/Y/Z values when HasGyroData/HasAccelData are true.

MousePreviewControl

Files: MousePreviewControl.xaml, MousePreviewControl.xaml.cs

Graphical mouse preview UserControl used on the Devices page for mouse-type input devices. Renders a stylized mouse outline with visual indicators for button presses, scroll wheel state, and movement delta.

Slot Toggle Buttons

ItemsControl of toggle buttons bound to ActiveSlotItems. Accent fill when IsAssigned. Click fires ToggleSlotCommand to assign/unassign the device to a controller slot.

Device Drag to Sidebar

Device cards support drag start via PreviewMouseLeftButtonDown + PreviewMouseMove. Dropping on a sidebar controller card assigns the device to that slot.

Input Hiding

The device detail panel includes per-device input hiding controls:

  • Hide from games (HidHide) toggle — hides the physical device using the HidHide driver
  • Consume mapped inputs toggle — suppresses keyboard/mouse inputs via low-level hooks
  • Input Mode section (gamepad devices only) — Force raw joystick mode toggle

KBMPreviewView

Files: KBMPreviewView.xaml, KBMPreviewView.xaml.cs

Interactive keyboard and mouse preview for Keyboard+Mouse virtual controller slots, shown on the Controller tab of the PadPage.

Layout

Two Canvas areas arranged horizontally:

  • KeyboardCanvas — Full QWERTY keyboard layout built programmatically from KeyboardKeyItem.BuildLayout(). Each key is a Border with a TextBlock label, positioned absolutely on the canvas. Keys highlight with accent color when the corresponding virtual key is pressed in the output state.
  • MouseCanvas — Stylized mouse graphic with contoured LMB/RMB paths around a scroll wheel pill, a movement circle with deflection dot, scroll direction arrows, and X1/X2 side buttons.

Interaction

All elements are clickable for click-to-record mapping — clicking a key or mouse element fires ControllerElementRecordRequested with the target name (e.g., KbmKey41 for a keyboard key, KbmMBtn0 for LMB, KbmMouseX for horizontal mouse movement). Hover highlights use a blue brush; recording targets use a 400ms flash timer with orange brush.

Rendering

Uses CompositionTarget.Rendering with a _dirty flag. Per frame:

  • Keyboard keys: reads KbmOutputSnapshot.GetKey() per VK index, sets accent background on pressed keys.
  • Mouse buttons: reads GetMouseButton() for LMB/RMB/MMB/X1/X2.
  • Movement dot: maps MouseDeltaX/MouseDeltaY to deflection within the movement circle.
  • Scroll arrows: lights up/down arrows based on ScrollDelta sign.

Tooltip Helper

MappingLabel() resolves target setting names to human-readable labels from the current mapping table, falling back to the raw target name.


MidiPreviewView

Files: MidiPreviewView.xaml, MidiPreviewView.xaml.cs

MIDI note and CC visualization for MIDI virtual controller slots, shown on the Controller tab of the PadPage.

Layout

A single Canvas (MidiCanvas) dynamically rebuilt when MidiSlotConfig properties change (start note, note count, start CC, CC count):

  • CC Sliders section — Vertical bar sliders, one per CC output. Each has a background rectangle, a fill rectangle that grows from the bottom proportional to the CC value (0-127), and a CC number label below.
  • Piano Keyboard section — Musically correct piano layout: white keys placed first (full height, underneath), black keys placed on top (shorter, narrower, higher Z-index) between adjacent white keys. White keys display note name + octave labels (e.g., "C4", "D4"). Note layout follows standard chromatic positions (C, C#, D, D#, E, F, F#, G, G#, A, A#, B).

Interaction

All CC slider backgrounds and piano keys are clickable for click-to-record (fires ControllerElementRecordRequested with MidiCC{index} or MidiNote{index}). Hover highlights and 400ms flash timer for recording targets, matching the pattern used by other preview views.

Rendering

Uses CompositionTarget.Rendering with a _dirty flag. Per frame:

  • CC sliders: reads MidiOutputSnapshot.CcValues[], scales fill height to 0-100%.
  • Piano keys: reads MidiOutputSnapshot.Notes[] boolean array, applies PressedBrush (blue tint for white keys, darker blue for black keys) on active notes.

Layout Rebuild

The entire canvas is rebuilt (cleared and reconstructed) when any MidiSlotConfig property changes — this handles dynamic changes to note/CC count and range without partial layout updates.


MousePreviewControl

Files: MousePreviewControl.xaml, MousePreviewControl.xaml.cs

Read-only mouse graphic used on the Devices page detail pane for mouse-type input devices.

Layout

Built once on Loaded into a Canvas (MouseCanvas). Renders the same mouse body shape as KBMPreviewView but without click-to-record interaction:

  • LMB/RMB — Contoured Path elements flanking the scroll wheel.
  • Scroll wheel pillRectangle with rounded corners between the buttons, plus up/down arrow Polygon indicators.
  • Movement circleEllipse with a deflection dot that tracks live mouse delta.
  • X1/X2 side buttons — Small Rectangle elements on the left edge of the mouse body.

Rendering

Uses CompositionTarget.Rendering (no dirty flag — renders every frame). Reads from the DevicesViewModel DataContext:

  • Button presses: RawButtons[0..4].IsPressed mapped to LMB, MMB, RMB, X1, X2.
  • Movement: MouseMotionX/MouseMotionY (normalized) mapped to dot deflection within the circle.
  • Scroll: MouseScrollIntensity drives arrow fill color, opacity, and scale transform for intensity feedback — arrows grow and brighten proportionally to scroll magnitude.

SettingsPage

Files: SettingsPage.xaml, SettingsPage.xaml.cs

Application settings organized in vertical card sections using CardBorder style.

Sections

Section Icon Contents
Language E774 Language ComboBox (10 languages), live switching
Appearance E790 Theme ComboBox (System Default / Light / Dark)
Input Engine E9F5 Auto-start toggle, polling on focus loss, polling interval NumberBox (1-16ms)
Window E737 Minimize to tray, start minimized, start at login checkboxes
HidHide Driver ED1A Status dot, version text, Install/Uninstall buttons, whitelist ListBox with Add/Remove buttons
ViGEmBus Driver E7FC Status dot, version text, Install/Uninstall buttons
vJoy Driver joystick SVG Status dot, version text, Install/Uninstall buttons
Settings File E8A5 File path, Save/Reload/Reset/Open Folder buttons, unsaved indicator
Diagnostics E9D9 App version, .NET runtime, SDL version

Driver sections use BoolToColorConverter for status dots and BoolToVisibilityConverter (with Invert parameter) to toggle Install/Uninstall button visibility.


ProfilesPage

Files: ProfilesPage.xaml, ProfilesPage.xaml.cs

Per-app profile management.

Layout

  • Auto-switch toggle: CheckBox bound to EnableAutoProfileSwitching.
  • Active profile info: TextBlock showing current profile name.
  • Profile ListBox: Each item shows profile name, executable list (trimmed), and topology badges (Xbox/DS4/vJoy/MIDI counts with mini SVG icons). Badges use DataTrigger on count = 0 to collapse empty types. "No slots" fallback badge shown when all counts are zero.
  • Action buttons: New, Save As, Edit, Load, Delete.

Profile ListBox Item Template

Each profile item contains:

  1. Profile name (FontWeight="SemiBold").
  2. Executable list (TextTrimming, collapsed when empty via DataTrigger).
  3. Type count badges in a StackPanel:
    • Xbox badge: Xbox SVG icon + count, collapsed when XboxCount == 0.
    • DS4 badge: PlayStation SVG icon + count, collapsed when DS4Count == 0.
    • vJoy badge: Joystick SVG icon + count, collapsed when VJoyCount == 0.
    • MIDI badge: Music note SVG icon + count, collapsed when MidiCount == 0.
    • "No slots" fallback when TopologyLabel == "No slots".

AboutPage

Files: AboutPage.xaml, AboutPage.xaml.cs

Static information page showing application name, version, copyright, license information, and links.


Dialog Windows

CopyFromDialog

Files: CopyFromDialog.xaml, CopyFromDialog.xaml.cs

Modal dialog for copying mappings from another slot's device. Lists all slots with their mapped devices for selection.

ProfileDialog

Files: ProfileDialog.xaml, ProfileDialog.xaml.cs

Modal dialog for creating/editing profiles. Fields: profile name, executable list (comma-separated).


Value Converters

All converters live in PadForge.App/Converter/ under the PadForge.Converters namespace and are registered as StaticResource in App.xaml.

Converter Key Input Output Description
AxisToPercentConverter AxisToPercentConverter ushort (0-65535) double (0-100) or string ("50.0%") Axis value to percentage. Returns formatted string when target type is string.
BoolToColorConverter BoolToColorConverter bool SolidColorBrush true = Green #4CAF50, false = Gray #9E9E9E. Brushes are frozen.
BoolToInstallTextConverter BoolToInstallTextConverter bool string Default: "Installed"/"Not Installed". With parameter "Action": "Uninstall"/"Install".
BoolToOpacityConverter BoolToOpacityConverter bool double true = 1.0, false = 0.2. Parameter: "falseVal" or "falseVal,trueVal".
BoolToVisibilityConverter BoolToVisibilityConverter bool Visibility true = Visible, false = Collapsed. Parameter "Invert" reverses. Supports ConvertBack.
NormToCanvasConverter NormToCanvasConverter double (0-1) double Canvas position. Parameter: "canvasDim" or "canvasDim,dotSize". Dot default 14px.
NormToTriggerHeightConverter NormToTriggerHeightConverter double (0-1) double Pixel height. Parameter = max size (default 40). Used for trigger bars and motor indicators.
NormToTriggerSlideConverter NormToTriggerSlideConverter double (0-1) double Canvas.Top for sliding bar. Parameter: "containerH,barH". 0 = bottom, 1 = top.
NullToCollapsedConverter NullToCollapsedConverter object Visibility Non-null = Visible, null = Collapsed.
PercentToSizeConverter PercentToSizeConverter int/double (0-100) double Pixel size from percentage. Parameter = max size. Used for dead zone ring visualization.
PovToAngleConverter PovToAngleConverter int (centidegrees) double (degrees) 0-35999 centidegrees to 0-359.99 degrees. Returns NaN for -1 (centered).
StatusToColorConverter StatusToColorConverter string SolidColorBrush "Running"/"Online"/"Connected" = Green, "Stopped"/"Offline"/"Error" = Red, "Warning" = Orange, other = Gray.
StringToGeometryConverter StringToGeometry string Geometry Parses SVG path data string to Geometry via Geometry.Parse().
StringToVisibilityConverter StringToVisibility string Visibility Non-null and non-empty = Visible, else Collapsed.

Resource Dictionaries and Theming

App.xaml Resource Hierarchy

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ui:ThemeResources>           <!-- ModernWPF theme (Light/Dark/HighContrast) -->
                <ui:ThemeResources.ThemeDictionaries>
                    <!-- Per-theme brush overrides: RangeSliderThumbFill, PopupBackgroundBrush -->
                </ui:ThemeResources.ThemeDictionaries>
            </ui:ThemeResources>
            <ui:XamlControlsResources />  <!-- ModernWPF control styles -->
            <ResourceDictionary Source="/Resources/ControllerIcons.xaml"/>
        </ResourceDictionary.MergedDictionaries>

        <!-- 14 global converter registrations -->
    </ResourceDictionary>
</Application.Resources>

ControllerIcons.xaml

File: PadForge.App/Resources/ControllerIcons.xaml

Contains:

DrawingImage icons (used in sidebar, dashboard, profiles):

Key Source Description
XboxControllerIcon svgrepo.com (32x32) Xbox logo icon
DS4ControllerIcon svgrepo.com (32x32) PlayStation logo icon
VJoyControllerIcon svgrepo.com (24x24) Joystick icon
MidiControllerIcon svgrepo.com MIDI / music note icon
GenericControllerIcon svgrepo.com (512x512, scaled) Generic gamepad with D-pad and face buttons

All icons use DynamicResource SystemControlForegroundBaseHighBrush as fill for automatic theme adaptation.

Shared card styles:

Key TargetType Properties
CardBorder Border ChromeMediumLow background, 8px corner radius, 16px padding, 12px bottom margin
CardTitle TextBlock 14px font, SemiBold weight
CardDescription TextBlock 12px font, 0.6 opacity, wrap enabled

Theme Switching

SettingsViewModel.SelectedThemeIndex maps to:

  • 0 = System Default (follows Windows setting)
  • 1 = Light
  • 2 = Dark

Applied via ThemeManager.Current.ApplicationTheme in OnThemeChanged handler.

Custom Per-Theme Brushes

Defined in ThemeDictionaries within App.xaml:

Brush Key Light Dark Usage
RangeSliderThumbFill #F0F0F0 #F0F0F0 RangeSlider custom thumb fill
PopupBackgroundBrush #FFE0E0E0 #FF3A3A3A Add controller popup background

Common XAML Patterns

Card-Based Layout

Settings and driver pages use a consistent card pattern:

<Border Style="{StaticResource CardBorder}">
    <StackPanel>
        <StackPanel Orientation="Horizontal" Margin="0,0,0,4">
            <TextBlock Text="&#xE790;" FontFamily="Segoe MDL2 Assets" FontSize="16"
                       VerticalAlignment="Center" Margin="0,0,8,0"/>
            <TextBlock Text="Card Title" Style="{StaticResource CardTitle}"/>
        </StackPanel>
        <TextBlock Text="Description text." Style="{StaticResource CardDescription}"/>
        <!-- Card content -->
    </StackPanel>
</Border>

Status Indicator Dots

Used throughout for online/offline and installed/not-installed states:

<Ellipse Width="8" Height="8"
         Fill="{Binding SomeBool, Converter={StaticResource BoolToColorConverter}}"
         VerticalAlignment="Center" Margin="0,0,8,0"/>

Segoe MDL2 Asset Icons

The app uses Segoe MDL2 Assets font for icon glyphs rather than image resources:

<TextBlock Text="&#xE713;" FontFamily="Segoe MDL2 Assets" FontSize="20"/>

Common codes used: E713 (settings gear), E790 (personalization), E9F5 (processing), E737 (star), ED1A (shield), E7FC (gamepad), E8A5 (save), E9D9 (bug), E8F1 (group), E8B9 (photo), F158 (3D).

ModernWPF NumberBox

Used for numeric input with inline spin buttons:

<ui:NumberBox Value="{Binding PollingRateMs, Mode=TwoWay}"
              Minimum="1" Maximum="16"
              SpinButtonPlacementMode="Inline" Width="120"/>

Code-Behind Patterns

Bind/Unbind Pattern

All three visualization views (3D, 2D, Schematic) follow the same interface:

public void Bind(PadViewModel vm)    // Subscribe to PropertyChanged, hook rendering, load model
public void Unbind()                 // Stop flash, unhook rendering, clear VM reference

This pattern allows PadPage to switch between views cleanly when the output type or user preference changes.

CompositionTarget.Rendering with Dirty Flag

Views use CompositionTarget.Rendering for per-frame visual updates, gated by a _dirty flag:

private bool _dirty;

private void OnRendering(object sender, EventArgs e)
{
    if (!_dirty || _vm == null) return;
    _dirty = false;
    // Update visuals...
}

private void OnVmPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    _dirty = true;  // Coalesce multiple property changes into one render frame
}

This batches multiple ViewModel property changes (button presses, axis movements) into a single visual update per render frame, avoiding redundant work.

DispatcherTimer Flash Animation

Used for "Map All" recording flow across all three views:

private DispatcherTimer _flashTimer;
private string _flashTarget;
private bool _flashOn;

private void UpdateFlashTarget(string target)
{
    // Start or stop flash timer based on target
    // Timer callback toggles highlight/default materials at 400ms (2D/3D) or 170ms (Schematic)
}

Clone this wiki locally