-
Notifications
You must be signed in to change notification settings - Fork 6
XAML Views
All views live in PadForge.App/Views/ under the PadForge.Views namespace. The app uses ModernWpfUI for Windows 10/11 Fluent Design styling.
Files: MainWindow.xaml, MainWindow.xaml.cs
The application shell. Contains the NavigationView sidebar, page content area, status bar, and driver overlay.
Two-row Grid:
-
Row 0 (star):
NavigationViewcontaining 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>PadForge does not use WPF Frame-based navigation. All pages are instantiated once in the XAML Grid and visibility-swapped:
-
NavView_SelectionChangedreads the selected item'sTagstring. - All page containers set to
Visibility.Collapsed. - The matching page set to
Visibility.Visible. - For controller slots (tag
"Pad:{index}"), the PadPage'sDataContextis set to the correspondingPadViewModel.
This approach preserves control state (scroll position, selected tabs, text field contents) across navigation since pages are never destroyed.
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.
Users can drag controller cards to reorder virtual controller slots:
-
OnCardDragStart—PreviewMouseLeftButtonDownrecords start position. -
OnNavViewDragMove—PreviewMouseMovechecks distance threshold, thenBeginCardDrag()creates aCardDragAdorner(ghost preview) andInsertionLineAdorner(drop indicator line). -
UpdateDragPosition— Updates adorner positions, computes target insertion index. -
EndCardDrag—PreviewMouseLeftButtonUpcompletes the swap. CallsSettingsManager.SwapSlots()andEnsureTypeGroupOrder().
Devices can be dragged from the Devices page card list to a sidebar controller card:
-
DevicesPageinitiatesDragDrop.DoDragDrop()withDeviceRowViewModelas the data payload. - Sidebar
NavigationViewItemhandlers (DragOver,Drop) accept the drop and assign the device to that slot.
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 fromPads[].OutputType. - All types share the global limit:
MaxXbox360Slots = MaxDS4Slots = MaxVJoySlots = MaxMidiSlots = MaxKeyboardMouseSlots = 16.
Bottom Border with four columns:
-
Status text —
StatusTextbinding, trimmed withCharacterEllipsis. -
Device count —
ConnectedDeviceCountwith "device(s)" suffix. -
Polling frequency —
PollingFrequencyformatted as"{0:F0} Hz". -
Engine indicator — Green/gray dot (
BoolToColorConverter) +EngineStatusText.
Semi-transparent overlay shown during driver install/uninstall operations:
-
ProgressRingspinner + text message. - Blocks all UI interaction (ZIndex=1000,
Grid.RowSpan="2"). - Shown/hidden by
RunDriverOperationAsync().
MainWindow.xaml.cs is the service wiring hub (~2600 lines). Constructor:
- Creates
MainViewModelas root ViewModel; setsDataContext. - Sets child
DataContext: Dashboard, Devices, Settings, Profiles pages. - Creates services:
SettingsService,InputService,RecorderService,DeviceService. - Wires all ViewModel events to services:
-
StartEngineRequested/StopEngineRequestedtoInputService.Start()/Stop(). -
SaveRequested/ReloadRequested/ResetRequestedtoSettingsService. - Driver install/uninstall commands to
DriverInstallermethods wrapped inRunDriverOperationAsync. -
TestRumbleRequested/TestLeftMotorRequested/TestRightMotorRequestedper pad. - Recording flow events per pad/mapping row.
- Profile management events (New, SaveAs, Edit, Load, Delete, RevertToDefault).
- Device assignment events via
DeviceService.
-
| 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 |
Files: DashboardPage.xaml, DashboardPage.xaml.cs
Overview page with engine toggle, slot summary cards, DSU settings, and driver status.
- Engine toggle: Start/Stop button with status indicator dot.
-
Slot cards:
WrapPanelinsideItemsControlbound toDashboardViewModel.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
ShowAddControlleris 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).
Files: PadPage.xaml, PadPage.xaml.cs
Configuration page for a single virtual controller slot. Contains a custom tab strip and 6 tab panels.
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
Tagproperty. -
Clickhandler callsvm.SelectedConfigTab = idx.
A hidden TabControl header via custom ControlTemplate provides the actual content switching. The TabControl's SelectedIndex is bound to SelectedConfigTab.
| 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 |
The Macros tab trigger section uses a two-tier layout:
-
Fire mode dropdown: Selects
MacroTriggerMode(OnPress,OnRelease,WhileHeld,Always). WhenAlwaysis selected, the Trigger Combination panel is hidden and a note explains the macro fires every frame. -
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 whenUsesAxisTriggeris true. -
POV trigger indicators: Shows recorded POV directions from
TriggerPovs.
-
Axis threshold slider: Bound to
The action list supports mouse action types (MouseMove, MouseButtonPress, MouseButtonRelease, MouseScroll) with sensitivity and mouse button dropdowns.
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
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
Hosts one of four views depending on output type and user settings:
-
ControllerModelView (3D, HelixToolkit) — for Xbox 360/DS4 when
Use2DControllerViewis false. -
ControllerModel2DView (2D overlays) — for Xbox 360/DS4 when
Use2DControllerViewis true. - ControllerSchematicView (procedural) — for custom vJoy (non-gamepad preset), always.
-
KBMPreviewView — for Keyboard+Mouse output type, always (see below).
MappingLabel()tooltip helper maps element names to human-readable labels. X1/X2 side buttonRectangleelements are promoted to named fields for flash highlight support. - 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:
- Unbinds all three views.
- Subscribes the active view's
ControllerElementRecordRequestedevent. - Calls
Bind(vm)on the active view.
All three views fire ControllerElementRecordRequested with a PadSetting target name string for click-to-record.
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.
Visible when OutputType == VJoy. Contains:
- Preset ComboBox (Xbox 360, DualShock 4, Custom).
- Four spinners visible only in Custom: ThumbstickCount, TriggerCount, PovCount, ButtonCount.
-
VJoyPresetCombo_SelectionChangedupdatesVJoyConfig.Presetand callsSyncVJoyCustomFields(). -
_syncingVJoyConfigguard prevents recursive updates during programmatic sync.
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.
ComboBox bound to MappedDevices with SelectedMappedDevice. Visible when multiple physical devices are assigned to one slot.
"Copy From" button opens CopyFromDialog to copy mappings from another slot's device.
Files: DevicesPage.xaml, DevicesPage.xaml.cs
Lists all detected input devices with raw input state visualization.
ListBox with card ItemTemplate. Each card shows:
- Status dot (green=online, gray=offline via
BoolToColorConverter) - Device name (bold)
- Slot badges (
SlotBadgescollection 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.
Shown when a device is selected (HasSelectedDevice). Contains:
-
Axes:
ItemsControlofProgressBarelements (0-100% range viaAxisToPercentConverter). Each bar shows name, normalized bar, and raw value. -
Buttons:
WrapPanelof circles. Accent fill when pressed (BoolToOpacityConverter). Button total label removed from display. For keyboard devices, displaysKeyboardKeyson a positionedCanvasinside aViewbox(QWERTY layout). For mouse devices, usesMousePreviewControlfor graphical preview. -
POV hats:
ItemsControlbound toRawPovs. Each POV displays as a compass with aRotateTransformarrow (PovToAngleConverterconverts centidegrees to degrees). -
Gyroscope/Accelerometer: Grid showing X/Y/Z values when
HasGyroData/HasAccelDataare true.
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.
ItemsControl of toggle buttons bound to ActiveSlotItems. Accent fill when IsAssigned. Click fires ToggleSlotCommand to assign/unassign the device to a controller slot.
Device cards support drag start via PreviewMouseLeftButtonDown + PreviewMouseMove. Dropping on a sidebar controller card assigns the device to that slot.
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
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.
Two Canvas areas arranged horizontally:
-
KeyboardCanvas — Full QWERTY keyboard layout built programmatically from
KeyboardKeyItem.BuildLayout(). Each key is aBorderwith aTextBlocklabel, 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.
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.
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/MouseDeltaYto deflection within the movement circle. - Scroll arrows: lights up/down arrows based on
ScrollDeltasign.
MappingLabel() resolves target setting names to human-readable labels from the current mapping table, falling back to the raw target name.
Files: MidiPreviewView.xaml, MidiPreviewView.xaml.cs
MIDI note and CC visualization for MIDI virtual controller slots, shown on the Controller tab of the PadPage.
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).
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.
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, appliesPressedBrush(blue tint for white keys, darker blue for black keys) on active notes.
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.
Files: MousePreviewControl.xaml, MousePreviewControl.xaml.cs
Read-only mouse graphic used on the Devices page detail pane for mouse-type input devices.
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
Pathelements flanking the scroll wheel. -
Scroll wheel pill —
Rectanglewith rounded corners between the buttons, plus up/down arrowPolygonindicators. -
Movement circle —
Ellipsewith a deflection dot that tracks live mouse delta. -
X1/X2 side buttons — Small
Rectangleelements on the left edge of the mouse body.
Uses CompositionTarget.Rendering (no dirty flag — renders every frame). Reads from the DevicesViewModel DataContext:
- Button presses:
RawButtons[0..4].IsPressedmapped to LMB, MMB, RMB, X1, X2. - Movement:
MouseMotionX/MouseMotionY(normalized) mapped to dot deflection within the circle. - Scroll:
MouseScrollIntensitydrives arrow fill color, opacity, and scale transform for intensity feedback — arrows grow and brighten proportionally to scroll magnitude.
Files: SettingsPage.xaml, SettingsPage.xaml.cs
Application settings organized in vertical card sections using CardBorder style.
| 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.
Files: ProfilesPage.xaml, ProfilesPage.xaml.cs
Per-app profile management.
-
Auto-switch toggle:
CheckBoxbound toEnableAutoProfileSwitching. -
Active profile info:
TextBlockshowing 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
DataTriggeron count = 0 to collapse empty types. "No slots" fallback badge shown when all counts are zero. - Action buttons: New, Save As, Edit, Load, Delete.
Each profile item contains:
- Profile name (
FontWeight="SemiBold"). - Executable list (
TextTrimming, collapsed when empty viaDataTrigger). - 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".
- Xbox badge: Xbox SVG icon + count, collapsed when
Files: AboutPage.xaml, AboutPage.xaml.cs
Static information page showing application name, version, copyright, license information, and links.
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.
Files: ProfileDialog.xaml, ProfileDialog.xaml.cs
Modal dialog for creating/editing profiles. Fields: profile name, executable list (comma-separated).
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. |
<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>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 |
SettingsViewModel.SelectedThemeIndex maps to:
- 0 = System Default (follows Windows setting)
- 1 = Light
- 2 = Dark
Applied via ThemeManager.Current.ApplicationTheme in OnThemeChanged handler.
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 |
Settings and driver pages use a consistent card pattern:
<Border Style="{StaticResource CardBorder}">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock Text="" 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>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"/>The app uses Segoe MDL2 Assets font for icon glyphs rather than image resources:
<TextBlock Text="" 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).
Used for numeric input with inline spin buttons:
<ui:NumberBox Value="{Binding PollingRateMs, Mode=TwoWay}"
Minimum="1" Maximum="16"
SpinButtonPlacementMode="Inline" Width="120"/>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 referenceThis pattern allows PadPage to switch between views cleanly when the output type or user preference changes.
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.
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)
}