1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +00:00

Workshop - Add --alt-login-callback startup argument

UI - Rounded corners on profile icons
This commit is contained in:
Robert 2025-12-11 23:54:50 +01:00
parent 06c5294e88
commit 5609065974
7 changed files with 59 additions and 32 deletions

View File

@ -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)
{ {

View File

@ -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>

View File

@ -27,8 +27,9 @@ public partial class VisualEditorView : ReactiveUserControl<VisualEditorViewMode
this.WhenActivated(d => this.WhenActivated(d =>
{ {
ViewModel!.AutoFitRequested += ViewModelOnAutoFitRequested; VisualEditorViewModel? viewModel = ViewModel;
Disposable.Create(() => ViewModel.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d); viewModel!.AutoFitRequested += ViewModelOnAutoFitRequested;
Disposable.Create(() => viewModel.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
}); });
this.WhenAnyValue(v => v.Bounds).Where(_ => !_movedByUser).Subscribe(_ => AutoFit(true)); this.WhenAnyValue(v => v.Bounds).Where(_ => !_movedByUser).Subscribe(_ => AutoFit(true));

View File

@ -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"

View File

@ -80,7 +80,7 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
await EnablePluginAndFeatures(result.Entry); await EnablePluginAndFeatures(result.Entry);
// If the entry is a profile, move it to the General profile category // If the entry is a profile, move it to the General profile category
else if (result.Entry?.EntryType == EntryType.Profile) else if (result.Entry?.EntryType == EntryType.Profile)
MoveProfileToGeneral(result.Entry); PrepareProfile(result.Entry);
return result.IsSuccess; return result.IsSuccess;
} }
@ -124,7 +124,7 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
} }
} }
private void MoveProfileToGeneral(InstalledEntry entry) private void PrepareProfile(InstalledEntry entry)
{ {
if (!entry.TryGetMetadata("ProfileId", out Guid profileId)) if (!entry.TryGetMetadata("ProfileId", out Guid profileId))
return; return;
@ -132,12 +132,18 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
ProfileConfiguration? profile = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId); ProfileConfiguration? profile = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId);
if (profile == null) if (profile == null)
return; return;
ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == "General") ?? _profileService.CreateProfileCategory("General", true); ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == "General") ?? _profileService.CreateProfileCategory("General", true);
if (category.ProfileConfigurations.Contains(profile)) if (category.ProfileConfigurations.Contains(profile))
return; return;
// Add the profile to the category
category.AddProfileConfiguration(profile, null); category.AddProfileConfiguration(profile, null);
// Suspend all but the first profile in the category
profile.IsSuspended = category.ProfileConfigurations.Count > 1;
_profileService.SaveProfileCategory(category); _profileService.SaveProfileCategory(category);
} }
} }

View File

@ -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" />

View File

@ -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
{ {