mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-12 13:28:33 +00:00
Compare commits
3 Commits
108cbaae3d
...
7f5b677cc3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f5b677cc3 | ||
|
|
5609065974 | ||
|
|
06c5294e88 |
@ -157,10 +157,11 @@ internal class DeviceService : IDeviceService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <param name="leftHanded"></param>
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void AutoArrangeDevices()
|
public void AutoArrangeDevices(bool leftHanded)
|
||||||
{
|
{
|
||||||
SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement();
|
SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement(leftHanded);
|
||||||
surfaceArrangement.Arrange(_devices);
|
surfaceArrangement.Arrange(_devices);
|
||||||
foreach (ArtemisDevice artemisDevice in _devices)
|
foreach (ArtemisDevice artemisDevice in _devices)
|
||||||
artemisDevice.ApplyDefaultCategories();
|
artemisDevice.ApplyDefaultCategories();
|
||||||
|
|||||||
@ -46,7 +46,8 @@ public interface IDeviceService : IArtemisService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies auto-arranging logic to the surface
|
/// Applies auto-arranging logic to the surface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void AutoArrangeDevices();
|
/// <param name="leftHanded"></param>
|
||||||
|
void AutoArrangeDevices(bool leftHanded);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apples the best available to the provided <see cref="ArtemisDevice" />
|
/// Apples the best available to the provided <see cref="ArtemisDevice" />
|
||||||
|
|||||||
@ -48,22 +48,42 @@ internal class SurfaceArrangement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static SurfaceArrangement GetDefaultArrangement()
|
internal static SurfaceArrangement GetDefaultArrangement(bool leftHanded)
|
||||||
{
|
{
|
||||||
SurfaceArrangement arrangement = new();
|
SurfaceArrangement arrangement = new();
|
||||||
|
|
||||||
SurfaceArrangementType keypad = arrangement.AddType(RGBDeviceType.Keypad, 1);
|
SurfaceArrangementType keyboard, keypad, mousepad, mouse;
|
||||||
keypad.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Equal, VerticalArrangementPosition.Equal, 20));
|
if (leftHanded)
|
||||||
|
{
|
||||||
|
mousepad = arrangement.AddType(RGBDeviceType.Mousepad, 1);
|
||||||
|
mousepad.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10));
|
||||||
|
|
||||||
SurfaceArrangementType keyboard = arrangement.AddType(RGBDeviceType.Keyboard, 1);
|
mouse = arrangement.AddType(RGBDeviceType.Mouse, 2);
|
||||||
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(keypad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 20));
|
mouse.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
|
||||||
|
mouse.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Center, 10));
|
||||||
|
|
||||||
SurfaceArrangementType mousepad = arrangement.AddType(RGBDeviceType.Mousepad, 1);
|
keyboard = arrangement.AddType(RGBDeviceType.Keyboard, 1);
|
||||||
mousepad.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10));
|
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10));
|
||||||
|
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(mouse, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 100));
|
||||||
|
|
||||||
SurfaceArrangementType mouse = arrangement.AddType(RGBDeviceType.Mouse, 2);
|
keypad = arrangement.AddType(RGBDeviceType.Keypad, 1);
|
||||||
mouse.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
|
keypad.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Equal, VerticalArrangementPosition.Equal, 20));
|
||||||
mouse.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Center, 100));
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
keypad = arrangement.AddType(RGBDeviceType.Keypad, 1);
|
||||||
|
keypad.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Equal, VerticalArrangementPosition.Equal, 20));
|
||||||
|
|
||||||
|
keyboard = arrangement.AddType(RGBDeviceType.Keyboard, 1);
|
||||||
|
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(keypad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 20));
|
||||||
|
|
||||||
|
mousepad = arrangement.AddType(RGBDeviceType.Mousepad, 1);
|
||||||
|
mousepad.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10));
|
||||||
|
|
||||||
|
mouse = arrangement.AddType(RGBDeviceType.Mouse, 2);
|
||||||
|
mouse.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
|
||||||
|
mouse.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Center, 100));
|
||||||
|
}
|
||||||
|
|
||||||
SurfaceArrangementType headset = arrangement.AddType(RGBDeviceType.Headset, 1);
|
SurfaceArrangementType headset = arrangement.AddType(RGBDeviceType.Headset, 1);
|
||||||
headset.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Bottom, 100));
|
headset.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Bottom, 100));
|
||||||
|
|||||||
@ -32,7 +32,7 @@ internal class SurfaceArrangementConfiguration
|
|||||||
public int MarginBottom { get; }
|
public int MarginBottom { get; }
|
||||||
public SurfaceArrangement SurfaceArrangement { get; set; }
|
public SurfaceArrangement SurfaceArrangement { get; set; }
|
||||||
|
|
||||||
public bool Apply(List<ArtemisDevice> devices)
|
public bool Apply(List<ArtemisDevice> devicesToArrange, List<ArtemisDevice> devices)
|
||||||
{
|
{
|
||||||
if (Anchor != null && !Anchor.HasDevices(devices))
|
if (Anchor != null && !Anchor.HasDevices(devices))
|
||||||
return false;
|
return false;
|
||||||
@ -42,10 +42,10 @@ internal class SurfaceArrangementConfiguration
|
|||||||
new SurfaceArrangementType(SurfaceArrangement, RGBDeviceType.All, 1).GetEdge(HorizontalPosition, VerticalPosition);
|
new SurfaceArrangementType(SurfaceArrangement, RGBDeviceType.All, 1).GetEdge(HorizontalPosition, VerticalPosition);
|
||||||
|
|
||||||
// Stack multiple devices of the same type vertically if they are wider than they are tall
|
// Stack multiple devices of the same type vertically if they are wider than they are tall
|
||||||
bool stackVertically = devices.Average(d => d.RgbDevice.Size.Width) >= devices.Average(d => d.RgbDevice.Size.Height);
|
bool stackVertically = devicesToArrange.Average(d => d.RgbDevice.Size.Width) >= devicesToArrange.Average(d => d.RgbDevice.Size.Height);
|
||||||
|
|
||||||
ArtemisDevice? previous = null;
|
ArtemisDevice? previous = null;
|
||||||
foreach (ArtemisDevice artemisDevice in devices)
|
foreach (ArtemisDevice artemisDevice in devicesToArrange)
|
||||||
{
|
{
|
||||||
if (previous != null)
|
if (previous != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -28,18 +28,18 @@ internal class SurfaceArrangementType
|
|||||||
|
|
||||||
public void Arrange(List<ArtemisDevice> devices)
|
public void Arrange(List<ArtemisDevice> devices)
|
||||||
{
|
{
|
||||||
devices = devices.Where(d => d.DeviceType == DeviceType).ToList();
|
List<ArtemisDevice> devicesToArrange = devices.Where(d => d.DeviceType == DeviceType).ToList();
|
||||||
if (!devices.Any())
|
if (!devicesToArrange.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AppliedConfiguration = null;
|
AppliedConfiguration = null;
|
||||||
foreach (SurfaceArrangementConfiguration configuration in Configurations)
|
foreach (SurfaceArrangementConfiguration configuration in Configurations)
|
||||||
{
|
{
|
||||||
bool applied = configuration.Apply(devices);
|
bool applied = configuration.Apply(devicesToArrange, devices);
|
||||||
if (applied)
|
if (applied)
|
||||||
{
|
{
|
||||||
AppliedConfiguration = configuration;
|
AppliedConfiguration = configuration;
|
||||||
foreach (ArtemisDevice artemisDevice in devices)
|
foreach (ArtemisDevice artemisDevice in devicesToArrange)
|
||||||
artemisDevice.ZIndex = ZIndex;
|
artemisDevice.ZIndex = ZIndex;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ internal class SurfaceArrangementType
|
|||||||
VerticalArrangementPosition.Equal,
|
VerticalArrangementPosition.Equal,
|
||||||
10
|
10
|
||||||
) {SurfaceArrangement = SurfaceArrangement};
|
) {SurfaceArrangement = SurfaceArrangement};
|
||||||
fallback.Apply(devices);
|
fallback.Apply(devicesToArrange, devices);
|
||||||
AppliedConfiguration = fallback;
|
AppliedConfiguration = fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Controls.Documents;
|
using Avalonia.Controls.Documents;
|
||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.LogicalTree;
|
using Avalonia.LogicalTree;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
@ -43,7 +42,7 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable
|
|||||||
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||||
{
|
{
|
||||||
Content = Enum.TryParse(ConfigurationIcon.IconName, true, out MaterialIconKind parsedIcon)
|
Content = Enum.TryParse(ConfigurationIcon.IconName, true, out MaterialIconKind parsedIcon)
|
||||||
? new MaterialIcon {Kind = parsedIcon!}
|
? new MaterialIcon {Kind = parsedIcon}
|
||||||
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||||
}
|
}
|
||||||
else if (ConfigurationIcon.IconBytes != null)
|
else if (ConfigurationIcon.IconBytes != null)
|
||||||
@ -65,19 +64,28 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_stream = new MemoryStream(ConfigurationIcon.IconBytes);
|
_stream = new MemoryStream(ConfigurationIcon.IconBytes);
|
||||||
if (!ConfigurationIcon.Fill)
|
Border border = new()
|
||||||
{
|
{
|
||||||
Content = new Image {Source = new Bitmap(_stream)};
|
CornerRadius = CornerRadius,
|
||||||
return;
|
ClipToBounds = true,
|
||||||
|
VerticalAlignment = VerticalAlignment.Stretch,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ConfigurationIcon.Fill)
|
||||||
|
{
|
||||||
|
// Fill mode: use Foreground as Background and the bitmap as opacity mask
|
||||||
|
border.Background = TextElement.GetForeground(this);
|
||||||
|
border.OpacityMask = new ImageBrush(new Bitmap(_stream));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Non-fill mode: place the image inside the rounded border
|
||||||
|
border.Child = new Image { Source = new Bitmap(_stream) };
|
||||||
}
|
}
|
||||||
|
|
||||||
Content = new Border
|
Content = border;
|
||||||
{
|
|
||||||
Background = TextElement.GetForeground(this),
|
|
||||||
VerticalAlignment = VerticalAlignment.Stretch,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
||||||
OpacityMask = new ImageBrush(new Bitmap(_stream))
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -185,12 +185,15 @@ internal class ProfileEditorService : IProfileEditorService
|
|||||||
{
|
{
|
||||||
// Activate the profile if one was provided
|
// Activate the profile if one was provided
|
||||||
if (profileConfiguration != null)
|
if (profileConfiguration != null)
|
||||||
|
{
|
||||||
|
_profileService.FocusProfile = profileConfiguration;
|
||||||
_profileService.ActivateProfile(profileConfiguration);
|
_profileService.ActivateProfile(profileConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
// If there is no profile configuration or module, deliberately set the override to null
|
// If there is no profile configuration or module, deliberately set the override to null
|
||||||
_moduleService.SetActivationOverride(profileConfiguration?.Module);
|
_moduleService.SetActivationOverride(profileConfiguration?.Module);
|
||||||
});
|
});
|
||||||
|
|
||||||
_profileService.FocusProfile = profileConfiguration;
|
|
||||||
_profileConfigurationSubject.OnNext(profileConfiguration);
|
_profileConfigurationSubject.OnNext(profileConfiguration);
|
||||||
|
|
||||||
ChangeTime(TimeSpan.Zero);
|
ChangeTime(TimeSpan.Zero);
|
||||||
|
|||||||
@ -21,11 +21,14 @@ public partial class DebugView : ReactiveAppWindow<DebugViewModel>
|
|||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
Observable.FromEventPattern(x => ViewModel!.ActivationRequested += x, x => ViewModel!.ActivationRequested -= x).Subscribe(_ =>
|
DebugViewModel vm = ViewModel!;
|
||||||
{
|
Observable.FromEventPattern(x => vm.ActivationRequested += x, x => vm.ActivationRequested -= x)
|
||||||
WindowState = WindowState.Normal;
|
.Subscribe(_ =>
|
||||||
Activate();
|
{
|
||||||
}).DisposeWith(d);
|
WindowState = WindowState.Normal;
|
||||||
|
Activate();
|
||||||
|
})
|
||||||
|
.DisposeWith(d);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,14 +19,11 @@ public partial class PluginSettingsWindowView : ReactiveAppWindow<PluginSettings
|
|||||||
|
|
||||||
this.WhenActivated(disposables =>
|
this.WhenActivated(disposables =>
|
||||||
{
|
{
|
||||||
Observable.FromEventPattern(
|
PluginSettingsWindowViewModel vm = ViewModel!;
|
||||||
x => ViewModel!.ConfigurationViewModel.CloseRequested += x,
|
Observable.FromEventPattern(x => vm.ConfigurationViewModel.CloseRequested += x, x => vm.ConfigurationViewModel.CloseRequested -= x)
|
||||||
x => ViewModel!.ConfigurationViewModel.CloseRequested -= x
|
|
||||||
)
|
|
||||||
.Subscribe(_ => Close())
|
.Subscribe(_ => Close())
|
||||||
.DisposeWith(disposables);
|
.DisposeWith(disposables);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -79,11 +79,6 @@ public partial class ProfileTreeViewModel : TreeItemViewModel
|
|||||||
|
|
||||||
public override bool SupportsChildren => true;
|
public override bool SupportsChildren => true;
|
||||||
|
|
||||||
public void UpdateCanPaste()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Task ExecuteDuplicate()
|
protected override Task ExecuteDuplicate()
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
|
|||||||
@ -89,7 +89,7 @@
|
|||||||
Background="{DynamicResource ControlFillColorDefaultBrush}"
|
Background="{DynamicResource ControlFillColorDefaultBrush}"
|
||||||
IsVisible="{CompiledBinding ProfileConfiguration, Converter={x:Static ObjectConverters.IsNotNull}}">
|
IsVisible="{CompiledBinding ProfileConfiguration, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
<StackPanel Orientation="Horizontal" Margin="8">
|
<StackPanel Orientation="Horizontal" Margin="8">
|
||||||
<shared:ProfileConfigurationIcon ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}" Width="18" Height="18" Margin="0 0 5 0" />
|
<shared:ProfileConfigurationIcon ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}" Width="18" Height="18" CornerRadius="3" Margin="0 0 5 0" />
|
||||||
<TextBlock Text="{CompiledBinding ProfileConfiguration.Name}" />
|
<TextBlock Text="{CompiledBinding ProfileConfiguration.Name}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
@ -27,8 +27,9 @@ public partial class VisualEditorView : ReactiveUserControl<VisualEditorViewMode
|
|||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
ViewModel!.AutoFitRequested += ViewModelOnAutoFitRequested;
|
VisualEditorViewModel vm = ViewModel!;
|
||||||
Disposable.Create(() => ViewModel.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
|
vm!.AutoFitRequested += ViewModelOnAutoFitRequested;
|
||||||
|
Disposable.Create(() => vm.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.WhenAnyValue(v => v.Bounds).Where(_ => !_movedByUser).Subscribe(_ => AutoFit(true));
|
this.WhenAnyValue(v => v.Bounds).Where(_ => !_movedByUser).Subscribe(_ => AutoFit(true));
|
||||||
|
|||||||
@ -19,7 +19,8 @@ public partial class SplashView : ReactiveWindow<SplashViewModel>
|
|||||||
#endif
|
#endif
|
||||||
this.WhenActivated(disposables =>
|
this.WhenActivated(disposables =>
|
||||||
{
|
{
|
||||||
Observable.FromEventPattern(x => ViewModel!.CoreService.Initialized += x, x => ViewModel!.CoreService.Initialized -= x)
|
SplashViewModel vm = ViewModel!;
|
||||||
|
Observable.FromEventPattern(x => vm.CoreService.Initialized += x, x => vm.CoreService.Initialized -= x)
|
||||||
.Subscribe(_ => Dispatcher.UIThread.Post(Close))
|
.Subscribe(_ => Dispatcher.UIThread.Post(Close))
|
||||||
.DisposeWith(disposables);
|
.DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -72,15 +72,20 @@
|
|||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
ContextFlyout="{StaticResource ProfileMenuFlyout}"
|
ContextFlyout="{StaticResource ProfileMenuFlyout}"
|
||||||
Classes.flyout-open="{CompiledBinding IsOpen, Source={StaticResource ProfileMenuFlyout}}">
|
Classes.flyout-open="{CompiledBinding IsOpen, Source={StaticResource ProfileMenuFlyout}}">
|
||||||
<Border CornerRadius="4" ClipToBounds="True" Grid.Column="0" Width="22" Height="22" Margin="0 0 5 0" VerticalAlignment="Center">
|
<shared:ProfileConfigurationIcon Grid.Column="0"
|
||||||
<shared:ProfileConfigurationIcon x:Name="ProfileIcon" ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}">
|
x:Name="ProfileIcon"
|
||||||
<shared:ProfileConfigurationIcon.Transitions>
|
VerticalAlignment="Center"
|
||||||
<Transitions>
|
ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}"
|
||||||
<DoubleTransition Property="Opacity" Duration="0:0:0.2" />
|
Width="22"
|
||||||
</Transitions>
|
Height="22"
|
||||||
</shared:ProfileConfigurationIcon.Transitions>
|
CornerRadius="4"
|
||||||
</shared:ProfileConfigurationIcon>
|
Margin="0 0 5 0">
|
||||||
</Border>
|
<shared:ProfileConfigurationIcon.Transitions>
|
||||||
|
<Transitions>
|
||||||
|
<DoubleTransition Property="Opacity" Duration="0:0:0.2" />
|
||||||
|
</Transitions>
|
||||||
|
</shared:ProfileConfigurationIcon.Transitions>
|
||||||
|
</shared:ProfileConfigurationIcon>
|
||||||
|
|
||||||
<Panel Grid.Column="1" HorizontalAlignment="Left">
|
<Panel Grid.Column="1" HorizontalAlignment="Left">
|
||||||
<TextBlock Classes="fadable"
|
<TextBlock Classes="fadable"
|
||||||
|
|||||||
@ -132,6 +132,6 @@ public class SidebarProfileConfigurationViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
public bool Matches(string s)
|
public bool Matches(string s)
|
||||||
{
|
{
|
||||||
return s == $"profile/{ProfileConfiguration.ProfileId}/editor";
|
return s.StartsWith($"profile/{ProfileConfiguration.ProfileId}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,20 +27,27 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
|||||||
private readonly IWorkshopService _workshopService;
|
private readonly IWorkshopService _workshopService;
|
||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
|
private readonly IProfileService _profileService;
|
||||||
private readonly ISettingsVmFactory _settingsVmFactory;
|
private readonly ISettingsVmFactory _settingsVmFactory;
|
||||||
private readonly Progress<StreamProgress> _progress = new();
|
private readonly Progress<StreamProgress> _progress = new();
|
||||||
|
|
||||||
[Notify] private bool _isInstalled;
|
[Notify] private bool _isInstalled;
|
||||||
[Notify] private bool _shouldInstall;
|
[Notify] private bool _shouldInstall;
|
||||||
[Notify] private float _installProgress;
|
[Notify] private float _installProgress;
|
||||||
|
|
||||||
public DefaultEntryItemViewModel(ILogger logger, IEntrySummary entry, IWorkshopService workshopService, IWindowService windowService, IPluginManagementService pluginManagementService,
|
public DefaultEntryItemViewModel(ILogger logger,
|
||||||
|
IEntrySummary entry,
|
||||||
|
IWorkshopService workshopService,
|
||||||
|
IWindowService windowService,
|
||||||
|
IPluginManagementService pluginManagementService,
|
||||||
|
IProfileService profileService,
|
||||||
ISettingsVmFactory settingsVmFactory)
|
ISettingsVmFactory settingsVmFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_workshopService = workshopService;
|
_workshopService = workshopService;
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
|
_profileService = profileService;
|
||||||
_settingsVmFactory = settingsVmFactory;
|
_settingsVmFactory = settingsVmFactory;
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
|
|
||||||
@ -62,19 +69,18 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
if (!result.IsSuccess)
|
if (!result.IsSuccess)
|
||||||
{
|
{
|
||||||
await _windowService.CreateContentDialog().WithTitle("Failed to install entry")
|
await _windowService.CreateContentDialog()
|
||||||
|
.WithTitle("Failed to install entry")
|
||||||
.WithContent($"Failed to install entry '{Entry.Name}' ({Entry.Id}): {result.Message}")
|
.WithContent($"Failed to install entry '{Entry.Name}' ({Entry.Id}): {result.Message}")
|
||||||
.WithCloseButtonText("Skip and continue")
|
.WithCloseButtonText("Skip and continue")
|
||||||
.ShowAsync();
|
.ShowAsync();
|
||||||
}
|
}
|
||||||
// If the entry is a plugin, enable the plugin and all features
|
// If the entry is a plugin, enable the plugin and all features
|
||||||
else if (result.Entry?.EntryType == EntryType.Plugin)
|
else if (result.Entry?.EntryType == EntryType.Plugin)
|
||||||
{
|
|
||||||
await EnablePluginAndFeatures(result.Entry);
|
await EnablePluginAndFeatures(result.Entry);
|
||||||
} else if (result.Entry?.EntryType == EntryType.Profile)
|
// If the entry is a profile, move it to the General profile category
|
||||||
{
|
else if (result.Entry?.EntryType == EntryType.Profile)
|
||||||
|
PrepareProfile(result.Entry);
|
||||||
}
|
|
||||||
|
|
||||||
return result.IsSuccess;
|
return result.IsSuccess;
|
||||||
}
|
}
|
||||||
@ -107,14 +113,37 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
|||||||
_logger.Warning(e, "Failed to enable plugin feature '{FeatureName}', skipping", pluginFeatureInfo.Name);
|
_logger.Warning(e, "Failed to enable plugin feature '{FeatureName}', skipping", pluginFeatureInfo.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the plugin has a mandatory settings window, open it and wait
|
// If the plugin has a mandatory settings window, open it and wait
|
||||||
if (plugin.ConfigurationDialog != null && plugin.ConfigurationDialog.IsMandatory)
|
if (plugin.ConfigurationDialog != null && plugin.ConfigurationDialog.IsMandatory)
|
||||||
{
|
{
|
||||||
if (plugin.Resolve(plugin.ConfigurationDialog.Type) is not PluginConfigurationViewModel viewModel)
|
if (plugin.Resolve(plugin.ConfigurationDialog.Type) is not PluginConfigurationViewModel viewModel)
|
||||||
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
|
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
|
||||||
|
|
||||||
await _windowService.ShowDialogAsync(new PluginSettingsWindowViewModel(viewModel));
|
await _windowService.ShowDialogAsync(new PluginSettingsWindowViewModel(viewModel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PrepareProfile(InstalledEntry entry)
|
||||||
|
{
|
||||||
|
if (!entry.TryGetMetadata("ProfileId", out Guid profileId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProfileConfiguration? profile = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId);
|
||||||
|
if (profile == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == "General") ?? _profileService.CreateProfileCategory("General", true);
|
||||||
|
if (category.ProfileConfigurations.Contains(profile))
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
// Add the profile to the category
|
||||||
|
category.AddProfileConfiguration(profile, null);
|
||||||
|
|
||||||
|
// Suspend all but the first profile in the category
|
||||||
|
profile.IsSuspended = category.ProfileConfigurations.Count > 1;
|
||||||
|
|
||||||
|
_profileService.SaveProfileCategory(category);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -24,15 +24,14 @@
|
|||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Margin="0 0 10 0"
|
Margin="0 0 10 0"
|
||||||
Width="280"
|
Width="280"
|
||||||
Height="280"
|
Height="280">
|
||||||
IsEnabled="False">
|
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<avalonia:MaterialIcon Kind="HandBackLeft" Width="150" Height="150" HorizontalAlignment="Center" />
|
<avalonia:MaterialIcon Kind="HandBackLeft" Width="150" Height="150" HorizontalAlignment="Center" />
|
||||||
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10 0 0">
|
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10 0 0">
|
||||||
Left-handed preset (NYI)
|
Left-handed preset
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock TextAlignment="Center" Classes="subtitle" TextWrapping="Wrap">
|
<TextBlock TextAlignment="Center" Classes="subtitle" TextWrapping="Wrap">
|
||||||
A preset with the mouse on the left side of the keyboard
|
A preset with the mouse on the left side of the keyboard (are you the 10%?)
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -27,9 +27,7 @@ public class SurfaceStepViewModel : WizardStepViewModel
|
|||||||
|
|
||||||
private void ExecuteSelectLayout(string layout)
|
private void ExecuteSelectLayout(string layout)
|
||||||
{
|
{
|
||||||
// TODO: Implement the layout
|
_deviceService.AutoArrangeDevices(layout == "left");
|
||||||
_deviceService.AutoArrangeDevices();
|
|
||||||
|
|
||||||
Wizard.ChangeScreen<SettingsStepViewModel>();
|
Wizard.ChangeScreen<SettingsStepViewModel>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -13,6 +13,7 @@ using Artemis.UI.Shared;
|
|||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
@ -180,11 +181,18 @@ public partial class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewMod
|
|||||||
|
|
||||||
private async Task ExecuteAutoArrange()
|
private async Task ExecuteAutoArrange()
|
||||||
{
|
{
|
||||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Auto-arrange layout", "Are you sure you want to auto-arrange your layout? Your current settings will be overwritten.");
|
ContentDialogResult contentDialogResult = await _windowService.CreateContentDialog()
|
||||||
if (!confirmed)
|
.WithTitle("Auto-arrange layout")
|
||||||
return;
|
.WithContent("Which preset would you like to apply? Your current settings will be overwritten.")
|
||||||
|
.HavingPrimaryButton(b => b.WithText("Left-handed preset"))
|
||||||
_deviceService.AutoArrangeDevices();
|
.HavingSecondaryButton(b => b.WithText("Right-handed preset"))
|
||||||
|
.WithCloseButtonText("Cancel")
|
||||||
|
.ShowAsync();
|
||||||
|
|
||||||
|
if (contentDialogResult == ContentDialogResult.Primary)
|
||||||
|
_deviceService.AutoArrangeDevices(true);
|
||||||
|
else if (contentDialogResult == ContentDialogResult.Secondary)
|
||||||
|
_deviceService.AutoArrangeDevices(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e)
|
private void RenderServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e)
|
||||||
|
|||||||
@ -33,16 +33,17 @@ public partial class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
|||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
ViewModel!.AutoFitRequested += ViewModelOnAutoFitRequested;
|
NodeScriptViewModel vm = ViewModel!;
|
||||||
ViewModel.PickerPositionSubject.Subscribe(ShowPickerAt).DisposeWith(d);
|
vm.AutoFitRequested += ViewModelOnAutoFitRequested;
|
||||||
if (ViewModel.IsPreview)
|
vm.PickerPositionSubject.Subscribe(ShowPickerAt).DisposeWith(d);
|
||||||
|
if (vm.IsPreview)
|
||||||
{
|
{
|
||||||
BoundsProperty.Changed.Subscribe(BoundsPropertyChanged).DisposeWith(d);
|
BoundsProperty.Changed.Subscribe(BoundsPropertyChanged).DisposeWith(d);
|
||||||
ViewModel.NodeViewModels.ToObservableChangeSet().Subscribe(_ => AutoFitIfPreview()).DisposeWith(d);
|
vm.NodeViewModels.ToObservableChangeSet().Subscribe(_ => AutoFitIfPreview()).DisposeWith(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() => AutoFit(true), DispatcherPriority.ContextIdle);
|
Dispatcher.UIThread.InvokeAsync(() => AutoFit(true), DispatcherPriority.ContextIdle);
|
||||||
Disposable.Create(() => ViewModel.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
|
Disposable.Create(() => vm.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,7 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
ConfigurationIcon="{CompiledBinding Icon}"
|
ConfigurationIcon="{CompiledBinding Icon}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
CornerRadius="4"
|
||||||
Width="22"
|
Width="22"
|
||||||
Height="22"
|
Height="22"
|
||||||
Margin="0 0 10 0" />
|
Margin="0 0 10 0" />
|
||||||
|
|||||||
@ -180,13 +180,15 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
{
|
{
|
||||||
await _authLock.WaitAsync(cancellationToken);
|
await _authLock.WaitAsync(cancellationToken);
|
||||||
|
|
||||||
|
// Start a HTTP listener, this port could be in use but chances are very slim
|
||||||
|
// IdentityServer only accepts these two redirect URLs
|
||||||
|
string redirectUri = Constants.StartupArguments.Contains("--alt-login-callback") ? "http://localhost:56789" : "http://localhost:57461";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_isLoggedInSubject.Value)
|
if (_isLoggedInSubject.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Start a HTTP listener, this port could be in use but chances are very slim
|
|
||||||
string redirectUri = "http://localhost:57461";
|
|
||||||
using HttpListener listener = new();
|
using HttpListener listener = new();
|
||||||
listener.Prefixes.Add(redirectUri + "/");
|
listener.Prefixes.Add(redirectUri + "/");
|
||||||
listener.Start();
|
listener.Start();
|
||||||
@ -249,7 +251,11 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
}
|
}
|
||||||
catch (HttpListenerException e)
|
catch (HttpListenerException e)
|
||||||
{
|
{
|
||||||
throw new ArtemisWebClientException($"HTTP listener for login callback failed with error code {e.ErrorCode}", e);
|
// I've seen the Nvidia app do this after a login. What are the odds...
|
||||||
|
if (e.ErrorCode == 32)
|
||||||
|
throw new ArtemisWebClientException($"HTTP listener for login callback failed because another application is already listening on '{redirectUri}', please close that application and try again", e);
|
||||||
|
else
|
||||||
|
throw new ArtemisWebClientException($"HTTP listener for login callback failed with error code {e.ErrorCode}", e);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user