Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertBeekman committed Jan 29, 2024
2 parents 65f6248 + cea8c1b commit ab64d13
Show file tree
Hide file tree
Showing 24 changed files with 321 additions and 93 deletions.
14 changes: 8 additions & 6 deletions src/Artemis.UI.Shared/Extensions/ArtemisLayoutExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ public static class ArtemisLayoutExtensions
/// Renders the layout to a bitmap.
/// </summary>
/// <param name="layout">The layout to render</param>
/// <param name="previewLeds">A value indicating whether or not to draw LEDs on the image.</param>
/// <param name="scale">The scale at which to draw the layout.</param>
/// <returns>The resulting bitmap.</returns>
public static RenderTargetBitmap RenderLayout(this ArtemisLayout layout, bool previewLeds)
public static RenderTargetBitmap RenderLayout(this ArtemisLayout layout, bool previewLeds, int scale = 2)
{
string? path = layout.Image?.LocalPath;

// Create a bitmap that'll be used to render the device and LED images just once
// Render 4 times the actual size of the device to make sure things look sharp when zoomed in
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) layout.RgbLayout.Width * 2, (int) layout.RgbLayout.Height * 2));
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) layout.RgbLayout.Width * scale, (int) layout.RgbLayout.Height * scale));

using DrawingContext context = renderTargetBitmap.CreateDrawingContext();

Expand All @@ -45,8 +47,8 @@ public static RenderTargetBitmap RenderLayout(this ArtemisLayout layout, bool pr
if (ledPath == null || !File.Exists(ledPath))
continue;
using Bitmap bitmap = new(ledPath);
using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(new PixelSize((led.RgbLayout.Width * 2).RoundToInt(), (led.RgbLayout.Height * 2).RoundToInt()));
context.DrawImage(scaledBitmap, new Rect(led.RgbLayout.X * 2, led.RgbLayout.Y * 2, scaledBitmap.Size.Width, scaledBitmap.Size.Height));
using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(new PixelSize((led.RgbLayout.Width * scale).RoundToInt(), (led.RgbLayout.Height * scale).RoundToInt()));
context.DrawImage(scaledBitmap, new Rect(led.RgbLayout.X * scale, led.RgbLayout.Y * scale, scaledBitmap.Size.Width, scaledBitmap.Size.Height));
}

if (!previewLeds)
Expand All @@ -55,14 +57,14 @@ public static RenderTargetBitmap RenderLayout(this ArtemisLayout layout, bool pr
// Draw LED geometry using a rainbow gradient
ColorGradient colors = ColorGradient.GetUnicornBarf();
colors.ToggleSeamless();
context.PushTransform(Matrix.CreateScale(2, 2));
context.PushTransform(Matrix.CreateScale(scale, scale));
foreach (ArtemisLedLayout led in layout.Leds)
{
Geometry? geometry = CreateLedGeometry(led);
if (geometry == null)
continue;

Color color = colors.GetColor((led.RgbLayout.X + led.RgbLayout.Width / 2) / layout.RgbLayout.Width).ToColor();
Color color = colors.GetColor((led.RgbLayout.X + led.RgbLayout.Width / scale) / layout.RgbLayout.Width).ToColor();
SolidColorBrush fillBrush = new() {Color = color, Opacity = 0.4};
SolidColorBrush penBrush = new() {Color = color};
Pen pen = new(penBrush) {LineJoin = PenLineJoin.Round};
Expand Down
6 changes: 6 additions & 0 deletions src/Artemis.UI.Shared/Routing/Router/IRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ public interface IRouter
/// <param name="options">Optional navigation options used to control navigation behaviour.</param>
/// <returns>A task representing the operation</returns>
Task Navigate(string path, RouterNavigationOptions? options = null);

/// <summary>
/// Asynchronously reloads the current route
/// </summary>
/// <returns>A task representing the operation</returns>
Task Reload();

/// <summary>
/// Asynchronously navigates back to the previous active route.
Expand Down
13 changes: 13 additions & 0 deletions src/Artemis.UI.Shared/Routing/Router/Router.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ public async Task Navigate(string path, RouterNavigationOptions? options = null)
await Dispatcher.UIThread.InvokeAsync(() => InternalNavigate(path, options));
}

/// <inheritdoc />
public async Task Reload()
{
string path = _currentRouteSubject.Value ?? "blank";

// Routing takes place on the UI thread with processing heavy tasks offloaded by the router itself
await Dispatcher.UIThread.InvokeAsync(async () =>
{
await InternalNavigate("blank", new RouterNavigationOptions {AddToHistory = false, RecycleScreens = false, EnableLogging = false});
await InternalNavigate(path, new RouterNavigationOptions {AddToHistory = false, RecycleScreens = false});
});
}

private async Task InternalNavigate(string path, RouterNavigationOptions options)
{
if (_root == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
x:Class="Artemis.UI.Screens.Debugger.Routing.RoutingDebugView"
x:DataType="routing:RoutingDebugViewModel">

<Grid RowDefinitions="Auto,Auto,*" ColumnDefinitions="*,Auto">
<Grid RowDefinitions="Auto,Auto,*" ColumnDefinitions="*,Auto,Auto">
<TextBox Grid.Row="0" Grid.Column="0" Watermark="Enter a route to navigate to" Text="{CompiledBinding Route}">
<TextBox.KeyBindings>
<KeyBinding Gesture="Enter" Command="{CompiledBinding Navigate}"></KeyBinding>
</TextBox.KeyBindings>
</TextBox>
<Button Grid.Row="0" Grid.Column="1" Margin="5 0 0 0" Command="{CompiledBinding Navigate}">Navigate</Button>
<Button Grid.Row="0" Grid.Column="1" Margin="5 0 0 0" Command="{CompiledBinding Reload}">Reload</Button>
<Button Grid.Row="0" Grid.Column="2" Margin="5 0 0 0" Command="{CompiledBinding Navigate}">Navigate</Button>

<TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="0 15">Navigation logs</TextBlock>
<ScrollViewer Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Name="LogsScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Margin="0 15">Navigation logs</TextBlock>
<ScrollViewer Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Name="LogsScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<SelectableTextBlock
Inlines="{CompiledBinding Lines}"
FontFamily="{StaticResource RobotoMono}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public RoutingDebugViewModel(IRouter router)
{
_router = router;
DisplayName = "Routing";
Reload = ReactiveCommand.CreateFromTask(ExecutReload);
Navigate = ReactiveCommand.CreateFromTask(ExecuteNavigate, this.WhenAnyValue(vm => vm.Route).Select(r => !string.IsNullOrWhiteSpace(r)));

_formatter = new MessageTemplateTextFormatter(
Expand All @@ -48,6 +49,7 @@ public RoutingDebugViewModel(IRouter router)
}

public InlineCollection Lines { get; } = new();
public ReactiveCommand<Unit, Unit> Reload { get; }
public ReactiveCommand<Unit, Unit> Navigate { get; }

private void OnLogEventAdded(object? sender, LogEventEventArgs e)
Expand Down Expand Up @@ -87,6 +89,18 @@ private void LimitLines()
if (Lines.Count > MAX_ENTRIES)
Lines.RemoveRange(0, Lines.Count - MAX_ENTRIES);
}

private async Task ExecutReload(CancellationToken arg)
{
try
{
await _router.Reload();
}
catch (Exception)
{
// ignored
}
}

private async Task ExecuteNavigate(CancellationToken arg)
{
Expand Down
3 changes: 2 additions & 1 deletion src/Artemis.UI/Screens/Root/BlankView.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;

namespace Artemis.UI.Screens.Root;

public partial class BlankView : UserControl
public partial class BlankView : ReactiveUserControl<BlankViewModel>
{
public BlankView()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ public class LayoutListViewModel : List.EntryListViewModel
public LayoutListViewModel(IWorkshopClient workshopClient,
IRouter router,
CategoriesViewModel categoriesViewModel,
List.EntryListInputViewModel entryListInputViewModel,
EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService,
Func<IGetEntries_Entries_Items, EntryListItemViewModel> getEntryListViewModel)
: base("workshop/entries/layout", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
: base("workshop/entries/layouts", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
{
entryListInputViewModel.SearchWatermark = "Search layouts";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,27 @@ namespace Artemis.UI.Screens.Workshop.Image;

public partial class ImagePropertiesDialogViewModel : ContentDialogViewModelBase
{
private readonly ImageUploadRequest _image;
[Notify] private string? _name;
[Notify] private string? _description;

public ImagePropertiesDialogViewModel(ImageUploadRequest image)
public ImagePropertiesDialogViewModel(string name, string description)
{
_image = image;
_name = image.Name;
_description = image.Description;

_name = string.IsNullOrWhiteSpace(name) ? null : name;
_description = string.IsNullOrWhiteSpace(description) ? null : description;
Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid);

this.ValidationRule(vm => vm.Name, input => !string.IsNullOrWhiteSpace(input), "Name is required");
this.ValidationRule(vm => vm.Name, input => input?.Length <= 50, "Name can be a maximum of 50 characters");
this.ValidationRule(vm => vm.Description, input => input?.Length <= 150, "Description can be a maximum of 150 characters");
this.ValidationRule(vm => vm.Description, input => input == null || input.Length <= 150, "Description can be a maximum of 150 characters");
}

public ReactiveCommand<Unit, Unit> Confirm { get; }

private void ExecuteConfirm()
{
if (string.IsNullOrWhiteSpace(Name))
if (!ValidationContext.IsValid)
return;

_image.Name = Name;
_image.Description = string.IsNullOrWhiteSpace(Description) ? null : Description;

ContentDialog?.Hide(ContentDialogResult.Primary);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<UserControl.Resources>
<converters:BytesToStringConverter x:Key="BytesToStringConverter" />
</UserControl.Resources>
<Border Classes="card" Padding="0" Width="300" ClipToBounds="True" Margin="5">
<Border Classes="card" Padding="0" ClipToBounds="True">
<Grid RowDefinitions="230,*">
<Rectangle Grid.Row="0" Fill="{DynamicResource CheckerboardBrush}" />
<Image Grid.Row="0"
Expand Down
63 changes: 48 additions & 15 deletions src/Artemis.UI/Screens/Workshop/Image/ImageSubmissionViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,64 +1,97 @@
using System;
using System.IO;
using System.Net.Http;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using System.Windows.Input;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;

namespace Artemis.UI.Screens.Workshop.Image;

public partial class ImageSubmissionViewModel : ValidatableViewModelBase
{
private readonly ImageUploadRequest _image;
private readonly IWindowService _windowService;
[Notify(Setter.Private)] private Bitmap? _bitmap;
[Notify(Setter.Private)] private string? _imageDimensions;
[Notify(Setter.Private)] private long _fileSize;
[Notify] private string? _name;
[Notify] private string? _description;
[Notify] private bool _hasChanges;
[Notify] private ICommand? _remove;

public ImageSubmissionViewModel(ImageUploadRequest image, IWindowService windowService)
public ImageSubmissionViewModel(ImageUploadRequest imageUploadRequest, IWindowService windowService)
{
_image = image;
ImageUploadRequest = imageUploadRequest;
_windowService = windowService;

FileSize = _image.File.Length;
Name = _image.Name;
Description = _image.Description;


FileSize = imageUploadRequest.File.Length;
Name = imageUploadRequest.Name;
Description = imageUploadRequest.Description;
HasChanges = true;

this.WhenActivated(d =>
{
Dispatcher.UIThread.Invoke(() =>
{
_image.File.Seek(0, SeekOrigin.Begin);
Bitmap = new Bitmap(_image.File);
imageUploadRequest.File.Seek(0, SeekOrigin.Begin);
Bitmap = new Bitmap(imageUploadRequest.File);
ImageDimensions = Bitmap.Size.Width + "x" + Bitmap.Size.Height;
Bitmap.DisposeWith(d);
}, DispatcherPriority.Background);
});
}

public ImageSubmissionViewModel(IImage existingImage, IWindowService windowService, IHttpClientFactory httpClientFactory)
{
_windowService = windowService;

Id = existingImage.Id;
Name = existingImage.Name;
Description = existingImage.Description;

// Download the image
this.WhenActivated(d =>
{
Dispatcher.UIThread.Invoke(async () =>
{
HttpClient client = httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
byte[] bytes = await client.GetByteArrayAsync($"/images/{existingImage.Id}.png");
MemoryStream stream = new(bytes);

Bitmap = new Bitmap(stream);
FileSize = stream.Length;
ImageDimensions = Bitmap.Size.Width + "x" + Bitmap.Size.Height;
Bitmap.DisposeWith(d);
}, DispatcherPriority.Background);
});

PropertyChanged += (_, args) => HasChanges = HasChanges || args.PropertyName == nameof(Name) || args.PropertyName == nameof(Description);
}

public ImageUploadRequest? ImageUploadRequest { get; }
public Guid? Id { get; }

public async Task<ContentDialogResult> Edit()
{
ContentDialogResult result = await _windowService.CreateContentDialog()
.WithTitle("Edit image properties")
.WithViewModel(out ImagePropertiesDialogViewModel vm, _image)
.WithViewModel(out ImagePropertiesDialogViewModel vm, Name ?? string.Empty, Description ?? string.Empty)
.HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Confirm))
.WithCloseButtonText("Cancel")
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
Name = _image.Name;
Description = _image.Description;

Name = vm.Name;
Description = vm.Description;

return result;
}
Expand Down
31 changes: 29 additions & 2 deletions src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<UserControl.Resources>
<converters:DateTimeConverter x:Key="DateTimeConverter" />
</UserControl.Resources>
<Grid ColumnDefinitions="300,*" RowDefinitions="*, Auto">
<Grid ColumnDefinitions="300,*,300" RowDefinitions="*, Auto">
<StackPanel Grid.Column="0" Grid.RowSpan="2" Spacing="10">
<Border Classes="card" VerticalAlignment="Top" Margin="0 0 10 0">
<StackPanel>
Expand Down Expand Up @@ -48,8 +48,35 @@
View workshop page
</controls:HyperlinkButton>
</StackPanel>

<ContentControl Grid.Column="1" Grid.Row="0" Content="{CompiledBinding EntrySpecificationsViewModel}"></ContentControl>
<StackPanel Grid.Column="1" Grid.Row="1" HorizontalAlignment="Right" Spacing="5" Orientation="Horizontal" Margin="0 10 0 0">

<Border Grid.Column="2" Grid.Row="0" Classes="card" Margin="10 0 0 0">
<Grid RowDefinitions="*,Auto">
<ScrollViewer Grid.Row="0" Classes="with-padding" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{CompiledBinding Images}">
<ItemsControl.Styles>
<Styles>
<Style Selector="ItemsControl > ContentPresenter">
<Setter Property="Margin" Value="0 0 0 10"></Setter>
</Style>
<Style Selector="ItemsControl > ContentPresenter:nth-last-child(1)">
<Setter Property="Margin" Value="0 0 0 0"></Setter>
</Style>
</Styles>
</ItemsControl.Styles>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
<Button Grid.Row="1" HorizontalAlignment="Stretch" Command="{CompiledBinding AddImage}">Add image</Button>
</Grid>
</Border>

<StackPanel Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" HorizontalAlignment="Right" Spacing="5" Orientation="Horizontal" Margin="0 10 0 0">
<Button Command="{CompiledBinding DiscardChanges}">Discard changes</Button>
<Button Command="{CompiledBinding SaveChanges}">Save</Button>
</StackPanel>
Expand Down
Loading

0 comments on commit ab64d13

Please sign in to comment.