Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
fc55a2f
Fix for WPF debug time
May 22, 2026
d4b25de
.editorconfig massive cleanup
May 22, 2026
4e7b1ab
Netural language set to "en"
May 22, 2026
9d68795
StartupWindow doesn't hide anymore, has its close button back, is asy…
May 23, 2026
52f6640
DynamicResource fix
May 29, 2026
4d4fd28
DesignTime hidden Settings panel
May 29, 2026
ac626a7
Minor code format
May 29, 2026
25aacec
ControlTreeViewWalkerCheckBox added + ProcessViewModel accepts ITreeV…
May 29, 2026
362089e
Tweaked .editorconfig
May 29, 2026
45e0bbc
Preventing .Hide and .Close to fail at dispose time
Jun 1, 2026
62cb8a8
Corrected "IsSupported" support
Jun 1, 2026
adc924a
Removed Binding Foreground and changed Binding TreeViewControl.Select…
Jun 1, 2026
9100648
Added a proper Children property list to items, plus LoadChildren dee…
Jun 1, 2026
1a590de
Minor code format
Jun 1, 2026
30d9540
WIP : reformat
Jun 1, 2026
f7b44b3
WIP: xaml correction + code correction
Jun 1, 2026
749e359
Multiple options allowed to be active at the same time.
Jun 2, 2026
02af440
Element duplication is gone, and element expansion does work as inten…
Jun 18, 2026
1f9280b
Overlay+Selection are colored in the TreeItems
Jun 18, 2026
148d023
Added icons to TreeViewItems
Jun 19, 2026
bd68a8c
Prise en charge partielle de davantage de ControlTypes.
Jun 19, 2026
0bcf7f6
ElementToSelectChanged's discarded return
Jun 19, 2026
05442ff
added current window as top TreeItem element
Jun 19, 2026
dffe839
wip doubleclick to overlay
Jun 19, 2026
2668857
major save bug correction
Jun 22, 2026
273fa64
really minor DefaultOverlay change
Jun 22, 2026
64167ee
Double-click on an element means focus+highlight
Jun 22, 2026
c2dc9a7
_patternsUiaXFunc -> _patternsUiaX
Jun 22, 2026
9f28513
CoppyTo -> CopyTo
Jun 22, 2026
38cee53
AsyncRelayCommand(_ => Init()) -> RelayCommand(_ => Init())
Jun 22, 2026
7e4a3a0
added SetOverlayOption
Jun 22, 2026
d71f277
got rid of WindowState.Minimized on CloseClick
Jun 22, 2026
1faf2c7
ProcessWindowOnClosed -> ProcessWindow_Closed
Jun 22, 2026
54f5b7c
window is restored and the nearest focusable element is focused
Jun 22, 2026
dd62dc7
added "Bring to front" contextmenu
Jun 22, 2026
f68561b
changed SetFocus to accept blink count+timer
Jun 22, 2026
85d3bca
== null -> is null
Jun 22, 2026
0e72a0d
moar is null everywhere
Jun 22, 2026
83adc8c
Hover mode doesn't freeze MainWindow at close time
Jun 22, 2026
6b5ae5b
now, with a real TreeView
Jun 22, 2026
56151d8
UI extraction
Jun 22, 2026
4f71e3b
now with moar options
Jun 22, 2026
fec10a3
cached XPath + added to the tooltip
Jun 23, 2026
960ac48
minor refinment
Jun 23, 2026
25fa2f9
expansion, retraction
Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
455 changes: 450 additions & 5 deletions .editorconfig

Large diffs are not rendered by default.

216 changes: 78 additions & 138 deletions src/FlaUInspect/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,153 +1,93 @@
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
using FlaUInspect.Core;
using FlaUInspect.Core.Logger;
using FlaUInspect.Settings;
using FlaUInspect.ViewModels;
using FlaUInspect.Views;
using Microsoft.Extensions.DependencyInjection;
using Color = System.Drawing.Color;
using wColor = System.Windows.Media.Color;

namespace FlaUInspect;

public partial class App {

public static IServiceProvider Services { get; private set; } = null!;
public static FlaUiAppOptions FlaUiAppOptions { get; } = new ();

public static InternalLogger Logger { get; } = new ();

protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);

ServiceCollection services = new ();
services.AddSingleton<ISettingsService<FlaUiAppSettings>>(_ => new JsonSettingsService<FlaUiAppSettings>(Path.Combine(AppContext.BaseDirectory, $"appsettings.json")));
Services = services.BuildServiceProvider();

ISettingsService<FlaUiAppSettings> settingsService = Services.GetRequiredService<ISettingsService<FlaUiAppSettings>>();
FlaUiAppSettings flaUiAppSettings = settingsService.Load();
ApplyAppOption(flaUiAppSettings);

//InternalLogger logger = new ();
Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
StartupViewModel startupViewModel = new ();
StartupWindow startupWindow = new (Logger) { DataContext = startupViewModel };
Current.MainWindow = startupWindow;
startupWindow.Show();

//Preload light theme
SetTheme(flaUiAppSettings);

Task.Run(() => startupViewModel.Init());

return;
AssemblyFileVersionAttribute? versionAttribute = Assembly.GetEntryAssembly()?.GetCustomAttribute(typeof(AssemblyFileVersionAttribute)) as AssemblyFileVersionAttribute;
string applicationVersion = versionAttribute?.Version ?? "N/A";
string windowHandle = string.Empty;

if (e.Args.Length > 0) {
windowHandle = e.Args[0];
}

#if AUTOMATION_UIA3
MainViewModel mainViewModel = new (AutomationType.UIA3, applicationVersion, windowHandle, logger);
MainWindow mainWindow = new () { DataContext = mainViewModel };

//Re-enable normal shutdown mode.
Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Current.MainWindow = mainWindow;
mainWindow.Show();
#elif AUTOMATION_UIA2
MainViewModel mainViewModel = new (AutomationType.UIA2, applicationVersion, windowHandle, logger);
MainWindow mainWindow = new() { DataContext = mainViewModel };

//Re-enable normal shutdown mode.
Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Current.MainWindow = mainWindow;
mainWindow.Show();
#else
// Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
// ChooseVersionWindow dialog = new ();
//
// if (dialog.ShowDialog() == true) {
//
// MainViewModel mainViewModel = new (dialog.SelectedAutomationType, applicationVersion, windowHandle, Logger);
// MainWindow mainWindow = new () { DataContext = mainViewModel };
//
// //Re-enable normal shutdown mode.
// Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
// Current.MainWindow = mainWindow;
// mainWindow.Show();
// }
#endif
}

public static void ApplyAppOption(FlaUiAppSettings settings) {
// Apply theme
Current.Dispatcher.Invoke(() => {
SetTheme(settings);
});

ThicknessConverter converter = new ();
FlaUiAppSettings cloneSetting = settings.Clone() as FlaUiAppSettings;

if (settings.HoverOverlay != null) {
Thickness hoverMargin = (Thickness)converter.ConvertFromString(cloneSetting.HoverOverlay.Margin);
FlaUiAppOptions.HoverOverlay = () => new ElementOverlay(
new ElementOverlayConfiguration(cloneSetting.HoverOverlay.Size,
hoverMargin,
ColorTranslator.FromHtml(cloneSetting.HoverOverlay.OverlayColor),
ElementOverlay.GetRectangleFactory(cloneSetting.HoverOverlay.OverlayMode)));
} else {
FlaUiAppOptions.HoverOverlay = FlaUiAppOptions.DefaultOverlay;
}

if (settings.SelectionOverlay != null) {
Thickness selectionMargin = (Thickness)converter.ConvertFromString(cloneSetting.SelectionOverlay.Margin);
FlaUiAppOptions.SelectionOverlay = () => new ElementOverlay(
new ElementOverlayConfiguration(cloneSetting.SelectionOverlay.Size,
selectionMargin,
ColorTranslator.FromHtml(cloneSetting.SelectionOverlay.OverlayColor),
ElementOverlay.GetRectangleFactory(cloneSetting.SelectionOverlay.OverlayMode)));
} else {
FlaUiAppOptions.SelectionOverlay = FlaUiAppOptions.DefaultOverlay;
}

if (settings.PickOverlay != null) {
Thickness pickMargin = (Thickness)converter.ConvertFromString(cloneSetting.PickOverlay.Margin);
FlaUiAppOptions.PickOverlay = () => new ElementOverlay(
new ElementOverlayConfiguration(cloneSetting.PickOverlay.Size,
pickMargin,
ColorTranslator.FromHtml(cloneSetting.PickOverlay.OverlayColor),
ElementOverlay.GetRectangleFactory(cloneSetting.PickOverlay.OverlayMode)));
} else {
FlaUiAppOptions.PickOverlay = FlaUiAppOptions.DefaultOverlay;
}
}

private static void SetTheme(FlaUiAppSettings settings) {
ResourceDictionary newTheme = new();

switch (settings.Theme) {
case "Dark":
newTheme.Source = new Uri("/FlaUInspect;component/Themes/DarkTheme.xaml", UriKind.Relative);
break;
default:
newTheme.Source = new Uri("/FlaUInspect;component/Themes/LightTheme.xaml", UriKind.Relative);
break;
}

// Remove existing theme dictionaries
for (int i = Current.Resources.MergedDictionaries.Count - 1; i >= 0; i--) {
ResourceDictionary dict = Current.Resources.MergedDictionaries[i];

if (dict.Source != null && (dict.Source.OriginalString.Contains("Themes/DarkTheme.xaml") || dict.Source.OriginalString.Contains("Themes/LightTheme.xaml"))) {
Current.Resources.MergedDictionaries.RemoveAt(i);
}
}

// Add the new theme dictionary
Current.Resources.MergedDictionaries.Add(newTheme);
}
public static IServiceProvider Services { get; private set; } = default!;
public static FlaUiAppOptions FlaUiAppOptions { get; } = new();

public static InternalLogger Logger { get; } = new();

protected override async void OnStartup(StartupEventArgs e) {
base.OnStartup(e);

ServiceCollection services = new();
_ = services.AddSingleton<ISettingsService<FlaUiAppSettings>>(_ => new JsonSettingsService<FlaUiAppSettings>(Path.Combine(AppContext.BaseDirectory, $"appsettings.json")));
Services = services.BuildServiceProvider();

var settingsService = Services.GetRequiredService<ISettingsService<FlaUiAppSettings>>();
var flaUiAppSettings = settingsService.Load();
ApplyAppOption(flaUiAppSettings);

//InternalLogger logger = new ();
Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
StartupViewModel startupViewModel = new();
StartupWindow startupWindow = new(Logger) { DataContext = startupViewModel };
Current.MainWindow = startupWindow;
startupWindow.Show();

//Preload light theme
SetTheme(flaUiAppSettings);

await Task.Run(startupViewModel.Init);
}

public static void ApplyAppOption(FlaUiAppSettings settings) {
// Apply theme
Current.Dispatcher.Invoke(() => SetTheme(settings));

SetOverlayOption(settings.HoverOverlay, o => FlaUiAppOptions.HoverOverlay = o);
SetOverlayOption(settings.SelectionOverlay, o => FlaUiAppOptions.SelectionOverlay = o);
SetOverlayOption(settings.PickOverlay, o => FlaUiAppOptions.PickOverlay = o);

// Add overlay colors as application resources
Current.Dispatcher.Invoke(() => SetOverlayColors(settings));
}

private static void SetOverlayOption(OverlaySettings? overlay, Action<Func<ElementOverlay?>> setter)
=> setter(overlay is not null ? (() => new(overlay)) : FlaUiAppOptions.DefaultOverlay);

private static void SetOverlayColors(FlaUiAppSettings settings) {
var pickColor = settings.PickOverlay is not null
? ColorTranslator.FromHtml(settings.PickOverlay.OverlayColor)
: Color.Blue;
var selectionColor = settings.SelectionOverlay is not null
? ColorTranslator.FromHtml(settings.SelectionOverlay.OverlayColor)
: Color.Blue;

Current.Resources["PickOverlayBrush"] = new SolidColorBrush(wColor.FromArgb(pickColor.A, pickColor.R, pickColor.G, pickColor.B));
Current.Resources["SelectionOverlayBrush"] = new SolidColorBrush(wColor.FromArgb(selectionColor.A, selectionColor.R, selectionColor.G, selectionColor.B));
}

private static void SetTheme(FlaUiAppSettings settings) {
ResourceDictionary newTheme = new() {
Source = settings.Theme switch {
"Dark" => new Uri("/FlaUInspect;component/Themes/DarkTheme.xaml", UriKind.Relative),
_ => new Uri("/FlaUInspect;component/Themes/LightTheme.xaml", UriKind.Relative),
}
};

// Remove existing theme dictionaries
for (var i = Current.Resources.MergedDictionaries.Count - 1; i >= 0; i--) {
var dict = Current.Resources.MergedDictionaries[i];

if (dict.Source is not null && (dict.Source.OriginalString.Contains("Themes/DarkTheme.xaml") || dict.Source.OriginalString.Contains("Themes/LightTheme.xaml")))
Current.Resources.MergedDictionaries.RemoveAt(i);
}

// Add the new theme dictionary
Current.Resources.MergedDictionaries.Add(newTheme);
}
}
10 changes: 5 additions & 5 deletions src/FlaUInspect/Controls/CheckBoxes.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@
<SolidColorBrush x:Key="OptionMark.Disabled.Background" Color="{DynamicResource PrimaryDisabledColor}"/>
<SolidColorBrush x:Key="OptionMark.Disabled.Border" Color="{DynamicResource DisabledTextColor}"/>
<SolidColorBrush x:Key="OptionMark.Disabled.Glyph" Color="{DynamicResource DisabledTextColor}"/>

<Style TargetType="{x:Type CheckBox}"
BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}" />
<Setter Property="Background" Value="{StaticResource PrimaryBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource PrimaryBorderBrush}" />
<Setter Property="FocusVisualStyle" Value="{DynamicResource FocusVisual}" />
<Setter Property="Background" Value="{DynamicResource PrimaryBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryBorderBrush}" />
<Setter Property="Foreground" Value="{DynamicResource TextBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Template">
Expand Down Expand Up @@ -121,5 +121,5 @@
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style>
</ResourceDictionary>
102 changes: 51 additions & 51 deletions src/FlaUInspect/Controls/TextBoxes.xaml
Original file line number Diff line number Diff line change
@@ -1,54 +1,54 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Style BasedOn="{StaticResource {x:Type TextBox}}"
TargetType="TextBox">
<Setter Property="Background" Value="{DynamicResource PrimaryBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryBorderBrush}" />
<Setter Property="Foreground" Value="{DynamicResource TextBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource PrimaryThickness}" />
<Setter Property="KeyboardNavigation.TabNavigation" Value="None" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="AllowDrop" Value="true" />
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource PrimaryCornerRadius}"
SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="border" Property="Opacity" Value="0.56" />
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="border" Property="BorderBrush" Value="{DynamicResource PrimaryHoverBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter TargetName="border" Property="BorderBrush" Value="{StaticResource SecondaryBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsInactiveSelectionHighlightEnabled" Value="true" />
<Condition Property="IsSelectionActive" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="SelectionBrush" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}" />
</MultiTrigger>
</Style.Triggers>
</Style>
<Style BasedOn="{StaticResource {x:Type TextBox}}"
TargetType="TextBox">
<Setter Property="Background" Value="{DynamicResource PrimaryBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryBorderBrush}" />
<Setter Property="Foreground" Value="{DynamicResource TextBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource PrimaryThickness}" />
<Setter Property="KeyboardNavigation.TabNavigation" Value="None" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="AllowDrop" Value="true" />
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource PrimaryCornerRadius}"
SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="border" Property="Opacity" Value="0.56" />
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="border" Property="BorderBrush" Value="{DynamicResource PrimaryHoverBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter TargetName="border" Property="BorderBrush" Value="{DynamicResource SecondaryBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsInactiveSelectionHighlightEnabled" Value="true" />
<Condition Property="IsSelectionActive" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="SelectionBrush" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}" />
</MultiTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
Loading