Compare commits

...

5 Commits

Author SHA1 Message Date
bd2e388fd0 invert liveview 2026-02-27 15:52:10 +01:00
857b06655f open picture fix 2026-02-25 20:50:19 +01:00
6740835be7 Weitere verbesserungen 2026-02-25 20:32:55 +01:00
3a3c780c07 herunterfahren button links unten 2026-02-24 21:07:34 +01:00
074f3ccb7f kamera fokussieren während timer läuft 2026-02-24 20:16:21 +01:00
16 changed files with 688 additions and 74 deletions

View File

@ -2,6 +2,5 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" /> <mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>

View File

@ -1,4 +1,5 @@
using System.Windows; using System.IO;
using System.Windows;
using CamBooth.App.Core.AppSettings; using CamBooth.App.Core.AppSettings;
using CamBooth.App.Core.Logging; using CamBooth.App.Core.Logging;
@ -25,31 +26,65 @@ public partial class App : Application
base.OnStartup(e); base.OnStartup(e);
var services = new ServiceCollection(); var services = new ServiceCollection();
ConfigureServices(services);
// Register base services
services.AddSingleton<Logger>();
services.AddSingleton<AppSettingsService>();
services.AddSingleton<PictureGalleryService>();
services.AddSingleton<CameraService>();
// Zuerst den Provider bauen, um AppSettings zu laden
var tempProvider = services.BuildServiceProvider();
var appSettings = tempProvider.GetRequiredService<AppSettingsService>();
var logger = tempProvider.GetRequiredService<Logger>();
// Stelle sicher, dass das PictureLocation-Verzeichnis existiert
try
{
if (!Directory.Exists(appSettings.PictureLocation))
{
Directory.CreateDirectory(appSettings.PictureLocation);
logger.Info($"Picture directory created: {appSettings.PictureLocation}");
}
}
catch (Exception ex)
{
logger.Error($"Failed to create picture directory: {ex.Message}");
}
// Jetzt die Camera Services basierend auf AppSettings registrieren
// Mit Try-Catch für fehlende DLL-Abhängigkeiten
try
{
if (appSettings.UseMockCamera)
{
services.AddSingleton<ICanonAPI, CanonAPIMock>();
services.AddSingleton<ICamera, CameraMock>();
}
else
{
services.AddSingleton<ICanonAPI, CanonAPI>();
services.AddSingleton<ICamera, Camera>();
}
}
catch (DllNotFoundException ex)
{
// Falls EDSDK DLL nicht gefunden, fallback auf Mock
MessageBox.Show(
$"EDSDK konnte nicht geladen werden. Verwende Mock-Kamera.\n\nFehler: {ex.Message}",
"DLL nicht gefunden",
MessageBoxButton.OK,
MessageBoxImage.Warning);
services.AddSingleton<ICanonAPI, CanonAPIMock>();
services.AddSingleton<ICamera, CameraMock>();
}
services.AddTransient<MainWindow>();
_serviceProvider = services.BuildServiceProvider(); _serviceProvider = services.BuildServiceProvider();
var mainWindow = _serviceProvider.GetRequiredService<MainWindow>(); var mainWindow = _serviceProvider.GetRequiredService<MainWindow>();
mainWindow.Show(); mainWindow.Show();
} }
private void ConfigureServices(IServiceCollection services)
{
// Register your services and view models here
services.AddTransient<MainWindow>();
services.AddSingleton<Logger>();
services.AddSingleton<AppSettingsService>();
services.AddSingleton<PictureGalleryService>();
services.AddSingleton<CameraService>();
#if DEBUG
services.AddSingleton<ICanonAPI, CanonAPIMock>();
services.AddSingleton<ICamera, CameraMock>();
#else
services.AddSingleton<ICanonAPI, CanonAPI>();
services.AddSingleton<ICamera, Camera>();
#endif
}
} }

View File

@ -29,10 +29,16 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Core\Models\" />
<Folder Include="Features\" /> <Folder Include="Features\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Remove="artifacts\**\*.cs" />
<EmbeddedResource Remove="artifacts\**\*" />
<None Remove="artifacts\**\*" />
<Page Remove="artifacts\**\*.xaml" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="Core\AppSettings\app.settings.json"> <None Update="Core\AppSettings\app.settings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>

View File

@ -1,4 +1,4 @@
using CamBooth.App.Core.Logging; using CamBooth.App.Core.Logging;
namespace CamBooth.App.Core.AppSettings; namespace CamBooth.App.Core.AppSettings;
@ -21,18 +21,43 @@ public class AppSettingsService
private void Initialize() private void Initialize()
{ {
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
loadedConfigFile = "Core/AppSettings/app.settings.json"; loadedConfigFile = "Core/AppSettings/app.settings.json";
#if DEBUG var configBuilder = new ConfigurationBuilder()
loadedConfigFile = "Core/AppSettings/app.settings.dev.json";
#endif
configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory) .SetBasePath(AppContext.BaseDirectory)
.AddJsonFile(loadedConfigFile, optional: false, reloadOnChange: true) .AddJsonFile(loadedConfigFile, optional: false, reloadOnChange: true);
.Build();
// Lade umgebungsspezifische Konfigurationsdatei
if (environment == "Development")
{
configBuilder.AddJsonFile("Core/AppSettings/app.settings.dev.json", optional: true, reloadOnChange: true);
loadedConfigFile = "Core/AppSettings/app.settings.dev.json";
} }
configuration = configBuilder.Build();
_logger.Info($"Konfiguration geladen aus: {loadedConfigFile} (Environment: {environment})");
}
public bool IsDebugMode => bool.Parse(configuration["AppSettings:IsDebugMode"] ?? "false");
public string? AppName => configuration["AppSettings:AppName"]; public string? AppName => configuration["AppSettings:AppName"];
public bool IsDebugConsoleVisible => bool.Parse(configuration["AppSettings:DebugConsoleVisible"] ?? string.Empty);
public string? PictureLocation => configuration["AppSettings:PictureLocation"]; public string? PictureLocation => configuration["AppSettings:PictureLocation"];
public int PhotoCountdownSeconds => int.Parse(configuration["AppSettings:PhotoCountdownSeconds"] ?? "5");
public int FocusDelaySeconds => int.Parse(configuration["AppSettings:FocusDelaySeconds"] ?? "2");
public int FocusTimeoutMs => int.Parse(configuration["AppSettings:FocusTimeoutMs"] ?? "3000");
public bool IsShutdownEnabled => bool.Parse(configuration["AppSettings:IsShutdownEnabled"] ?? "false");
public bool UseMockCamera => bool.Parse(configuration["AppSettings:UseMockCamera"] ?? "false");
public string? ConnectionString => configuration.GetConnectionString("DefaultConnection"); public string? ConnectionString => configuration.GetConnectionString("DefaultConnection");
public string ConfigFileName => loadedConfigFile; public string ConfigFileName => loadedConfigFile;

View File

@ -2,7 +2,14 @@
"AppSettings": { "AppSettings": {
"AppName": "Meine Anwendung", "AppName": "Meine Anwendung",
"Version": "1.0.0", "Version": "1.0.0",
"PictureLocation": "C:\\tmp\\cambooth" "IsDebugMode": true,
"PictureLocation": "C:\\tmp\\cambooth",
"DebugConsoleVisible": "true",
"PhotoCountdownSeconds": 2,
"FocusDelaySeconds": 1,
"FocusTimeoutMs": 1000,
"IsShutdownEnabled": false,
"UseMockCamera": true
}, },
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Server=myServer;Database=myDB;User Id=myUser;Password=myPassword;" "DefaultConnection": "Server=myServer;Database=myDB;User Id=myUser;Password=myPassword;"

View File

@ -2,7 +2,14 @@
"AppSettings": { "AppSettings": {
"AppName": "Meine Anwendung", "AppName": "Meine Anwendung",
"Version": "1.0.0", "Version": "1.0.0",
"PictureLocation": "C:\\cambooth\\pictures" "IsDebugMode": false,
"PictureLocation": "C:\\cambooth\\pictures",
"DebugConsoleVisible": "false",
"PhotoCountdownSeconds": 5,
"FocusDelaySeconds": 2,
"FocusTimeoutMs": 3000,
"IsShutdownEnabled": true,
"UseMockCamera": true
}, },
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Server=myServer;Database=myDB;User Id=myUser;Password=myPassword;" "DefaultConnection": "Server=myServer;Database=myDB;User Id=myUser;Password=myPassword;"

View File

@ -1,4 +1,5 @@
using System.IO; using System.IO;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using CamBooth.App.Core.AppSettings; using CamBooth.App.Core.AppSettings;
@ -192,6 +193,53 @@ public class CameraService : IDisposable
throw; throw;
} }
} }
public async Task PrepareFocusAsync(int focusTimeoutMs = 1500)
{
if (this._mainCamera is not EOSDigital.API.Camera sdkCamera)
{
await Task.Delay(200);
return;
}
var focusCompleted = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
void FocusStateChanged(EOSDigital.API.Camera sender, StateEventID eventId, int parameter)
{
if (eventId == StateEventID.AfResult)
{
focusCompleted.TrySetResult(true);
}
}
sdkCamera.StateChanged += FocusStateChanged;
try
{
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Halfway));
var completedTask = await Task.WhenAny(focusCompleted.Task, Task.Delay(focusTimeoutMs));
if (completedTask != focusCompleted.Task)
{
this._logger.Info("Autofocus timeout reached, continuing with countdown.");
}
}
catch (Exception ex)
{
this.ReportError(ex.Message);
}
finally
{
try
{
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF));
}
catch (Exception ex)
{
this.ReportError(ex.Message);
}
sdkCamera.StateChanged -= FocusStateChanged;
}
}
#region API Events #region API Events
@ -269,7 +317,10 @@ public class CameraService : IDisposable
Info.FileName = $"img_{Guid.NewGuid().ToString()}.jpg"; Info.FileName = $"img_{Guid.NewGuid().ToString()}.jpg";
sender.DownloadFile(Info, this._appSettings.PictureLocation); sender.DownloadFile(Info, this._appSettings.PictureLocation);
this._logger.Info("Download complete: " + Path.Combine(this._appSettings.PictureLocation, Info.FileName)); this._logger.Info("Download complete: " + Path.Combine(this._appSettings.PictureLocation, Info.FileName));
Application.Current.Dispatcher.Invoke(() => { this._pictureGalleryService.LoadThumbnailsToCache(); }); Application.Current.Dispatcher.Invoke(() => {
this._pictureGalleryService.IncrementNewPhotoCount();
this._pictureGalleryService.LoadThumbnailsToCache();
});
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -291,3 +342,4 @@ public class CameraService : IDisposable
#endregion #endregion
} }

View File

@ -14,7 +14,6 @@ public partial class DebugConsolePage : Page
this.InitializeComponent(); this.InitializeComponent();
} }
private void Logger_OnErrorLog(string text) private void Logger_OnErrorLog(string text)
{ {
this.tbDebugOutput.Text = this.tbDebugOutput.Text.Insert(0, text + "\n"); this.tbDebugOutput.Text = this.tbDebugOutput.Text.Insert(0, text + "\n");

View File

@ -32,6 +32,11 @@ public partial class LiveViewPage : Page
this._cameraService = cameraService; this._cameraService = cameraService;
this.InitializeComponent(); this.InitializeComponent();
this.SetImageAction = img => { this.bgbrush.ImageSource = img; }; this.SetImageAction = img => { this.bgbrush.ImageSource = img; };
// Mirror the LiveView image horizontally
ScaleTransform scaleTransform = new ScaleTransform(-1, 1, 0.5, 0.5);
this.bgbrush.Transform = scaleTransform;
this.LVCanvas.Background = this.bgbrush; this.LVCanvas.Background = this.bgbrush;
cameraService.ConnectCamera(); cameraService.ConnectCamera();
cameraService._mainCamera.LiveViewUpdated += this.MainCamera_OnLiveViewUpdated; cameraService._mainCamera.LiveViewUpdated += this.MainCamera_OnLiveViewUpdated;

View File

@ -9,7 +9,15 @@
Background="Black"> Background="Black">
<Grid> <Grid>
<WrapPanel VerticalAlignment="Stretch" Background="Black" x:Name="PicturesPanel" Orientation="Horizontal" /> <ScrollViewer x:Name="GalleryScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Background="Black" CanContentScroll="False">
<ItemsControl x:Name="PicturesPanel" Background="Black">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
<ContentPresenter x:Name="RootContentDialogPresenter" Grid.Row="0" /> <ContentPresenter x:Name="RootContentDialogPresenter" Grid.Row="0" />
</Grid> </Grid>
</Page> </Page>

View File

@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Media; using System.Windows.Media;
@ -24,6 +24,8 @@ public partial class PictureGalleryPage : Page
private readonly PictureGalleryService _pictureGalleryService; private readonly PictureGalleryService _pictureGalleryService;
private ContentDialog? _openContentDialog;
public PictureGalleryPage(AppSettingsService appSettingsService, Logger logger, PictureGalleryService pictureGalleryService) public PictureGalleryPage(AppSettingsService appSettingsService, Logger logger, PictureGalleryService pictureGalleryService)
{ {
@ -82,7 +84,7 @@ public partial class PictureGalleryPage : Page
hyperlink.Inlines.Add(new InlineUIContainer(imageControl)); hyperlink.Inlines.Add(new InlineUIContainer(imageControl));
textBlock.Inlines.Add(hyperlink); textBlock.Inlines.Add(hyperlink);
this.PicturesPanel.Children.Add(textBlock); this.PicturesPanel.Items.Add(textBlock);
loop++; loop++;
} }
while ((loop < howManyPictures || howManyPictures == 0) && loop < this._pictureGalleryService.ThumbnailsOrderedByNewestDescending.Count); while ((loop < howManyPictures || howManyPictures == 0) && loop < this._pictureGalleryService.ThumbnailsOrderedByNewestDescending.Count);
@ -90,19 +92,52 @@ public partial class PictureGalleryPage : Page
} }
public void CloseOpenDialog()
{
void CloseDialog()
{
if (this._openContentDialog is null)
{
return;
}
this._openContentDialog.ButtonClicked -= this.ContentDialog_OnButtonClicked;
this._openContentDialog.GetType().GetMethod("Hide", Type.EmptyTypes)?.Invoke(this._openContentDialog, null);
this.RootContentDialogPresenter.Content = null;
this._openContentDialog = null;
}
if (this.Dispatcher.CheckAccess())
{
CloseDialog();
return;
}
this.Dispatcher.Invoke(CloseDialog);
}
private void Hyperlink_OnClick(object sender, RoutedEventArgs e) private void Hyperlink_OnClick(object sender, RoutedEventArgs e)
{ {
Uri? picturePathUri = ((Hyperlink)sender).Tag as Uri; Uri? picturePathUri = ((Hyperlink)sender).Tag as Uri;
if (picturePathUri is null)
{
return;
}
string picturePath = Uri.UnescapeDataString(picturePathUri.AbsolutePath);
Application.Current.Dispatcher.BeginInvoke( Application.Current.Dispatcher.BeginInvoke(
async () => async () =>
{ {
this.CloseOpenDialog();
ContentDialog contentDialog = new(this.RootContentDialogPresenter); ContentDialog contentDialog = new(this.RootContentDialogPresenter);
this._openContentDialog = contentDialog;
Image imageToShow = new() Image imageToShow = new()
{ {
MaxHeight = 570, MaxHeight = 570,
Background = new SolidColorBrush(Colors.White), Background = new SolidColorBrush(Colors.White),
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
Source = PictureGalleryService.CreateThumbnail(picturePathUri.AbsolutePath, 450, 300) Source = PictureGalleryService.CreateThumbnail(picturePath, 450, 300)
}; };
contentDialog.VerticalAlignment = VerticalAlignment.Top; contentDialog.VerticalAlignment = VerticalAlignment.Top;
@ -117,10 +152,23 @@ public partial class PictureGalleryPage : Page
// contentDialog.SetCurrentValue(ContentDialog.PrimaryButtonIconProperty, PictureGalleryService.CreateRegularSymbolIcon(SymbolRegular.Print48, Colors.Tomato)); // contentDialog.SetCurrentValue(ContentDialog.PrimaryButtonIconProperty, PictureGalleryService.CreateRegularSymbolIcon(SymbolRegular.Print48, Colors.Tomato));
contentDialog.Tag = picturePathUri.AbsolutePath; contentDialog.Tag = picturePath;
contentDialog.ButtonClicked += this.ContentDialog_OnButtonClicked; contentDialog.ButtonClicked += this.ContentDialog_OnButtonClicked;
try
{
await contentDialog.ShowAsync(); await contentDialog.ShowAsync();
}
finally
{
contentDialog.ButtonClicked -= this.ContentDialog_OnButtonClicked;
if (ReferenceEquals(this._openContentDialog, contentDialog))
{
this._openContentDialog = null;
}
}
}); });
} }
} }

View File

@ -1,4 +1,4 @@
using System.IO; using System.IO;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
@ -17,6 +17,10 @@ public class PictureGalleryService
private readonly Dictionary<DateTime, BitmapImage> thumbnails = new(); private readonly Dictionary<DateTime, BitmapImage> thumbnails = new();
private int _newPhotoCount = 0;
public event EventHandler<int>? NewPhotoCountChanged;
public PictureGalleryService(AppSettingsService appSettings, Logger logger) public PictureGalleryService(AppSettingsService appSettings, Logger logger)
{ {
@ -30,6 +34,25 @@ public class PictureGalleryService
.Select(ordered => ordered.Value) .Select(ordered => ordered.Value)
.ToList(); .ToList();
public int NewPhotoCount => _newPhotoCount;
public void IncrementNewPhotoCount()
{
_newPhotoCount++;
OnNewPhotoCountChanged(_newPhotoCount);
}
public void ResetNewPhotoCount()
{
_newPhotoCount = 0;
OnNewPhotoCountChanged(_newPhotoCount);
}
private void OnNewPhotoCountChanged(int count)
{
NewPhotoCountChanged?.Invoke(this, count);
}
public async Task LoadThumbnailsToCache(int cacheSize = 0) public async Task LoadThumbnailsToCache(int cacheSize = 0)
{ {
@ -37,7 +60,33 @@ public class PictureGalleryService
string? pictureLocation = this._appSettings.PictureLocation; string? pictureLocation = this._appSettings.PictureLocation;
int loop = 0; int loop = 0;
List<string> picturePaths = Directory.EnumerateFiles(pictureLocation).ToList(); // Sicherstellen, dass das Verzeichnis existiert
if (!Directory.Exists(pictureLocation))
{
this._logger.Info($"Picture directory does not exist: '{pictureLocation}'. Creating it...");
try
{
Directory.CreateDirectory(pictureLocation);
this._logger.Info($"Picture directory created: '{pictureLocation}'");
}
catch (Exception ex)
{
this._logger.Error($"Failed to create picture directory: {ex.Message}");
return;
}
}
// Filter nur Bilddateien
string[] imageExtensions = { ".jpg", ".jpeg", ".png", ".bmp", ".gif" };
List<string> picturePaths = Directory.EnumerateFiles(pictureLocation)
.Where(f => imageExtensions.Contains(Path.GetExtension(f).ToLower()))
.ToList();
if (picturePaths.Count == 0)
{
this._logger.Info($"No pictures found in directory: '{pictureLocation}'");
return;
}
await Task.Run( await Task.Run(
() => () =>

View File

@ -27,7 +27,7 @@
x:Name="MainFrame" x:Name="MainFrame"
NavigationUIVisibility="Hidden" NavigationUIVisibility="Hidden"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Background="LightBlue" Background="Black"
VerticalAlignment="Center" VerticalAlignment="Center"
Panel.ZIndex="0" /> Panel.ZIndex="0" />
@ -41,21 +41,27 @@
Panel.ZIndex="1" /> Panel.ZIndex="1" />
<!-- Inhalt der dritten Zeile --> <!-- Inhalt der dritten Zeile -->
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Bottom" Visibility="Hidden" Name="TimerPanel" Background="#AA000000" Panel.ZIndex="2" <StackPanel Grid.Row="0" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Bottom" Visibility="Hidden" Name="TimerPanel" Background="#AA000000" Panel.ZIndex="2"
Margin="0 0 0 0"> Margin="0 0 0 0">
<TextBlock x:Name="CaptureStatusText"
Text="Scharfstellen..."
Visibility="Collapsed"
Foreground="White"
FontSize="36"
FontWeight="Bold"
HorizontalAlignment="Center"
Margin="24 24 24 12"/>
<liveView:TimerControlRectangleAnimation x:Name="TimerControlRectangleAnimation" HorizontalAlignment="Center" VerticalAlignment="Center"/> <liveView:TimerControlRectangleAnimation x:Name="TimerControlRectangleAnimation" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Bottom" Name="ButtonPanel" Background="Transparent" Panel.ZIndex="2" <StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Bottom" Name="ButtonPanel" Background="Transparent" Panel.ZIndex="2"
Visibility="Hidden"
Margin="0 0 0 0"> Margin="0 0 0 0">
<ui:Button Content="Start" Click="NavToLiveView" Width="200" Height="75" VerticalAlignment="Bottom" <ui:Button x:Name="HideDebugButton" Content="Hide Debug" Click="SetVisibilityDebugConsole" Width="200" Height="75"
Margin="0 0 5 0" />
<ui:Button Content="Hide Debug" Click="SetVisibilityDebugConsole" Width="200" Height="75"
VerticalAlignment="Bottom" Margin="0 0 5 0" /> VerticalAlignment="Bottom" Margin="0 0 5 0" />
<!-- <ui:Button Content="Take Photo" Click="StartTakePhotoProcess" Width="200" Height="75" VerticalAlignment="Bottom" --> <!-- <ui:Button Content="Take Photo" Click="StartTakePhotoProcess" Width="200" Height="75" VerticalAlignment="Bottom" -->
<!-- Margin="0 0 5 0" /> --> <!-- Margin="0 0 5 0" /> -->
<Button Width="160" Height="160" <Button Width="160" Height="160"
Click="StartTakePhotoProcess" Click="StartTakePhotoProcess"
Content="PUSH ME"
FontSize="64" FontSize="64"
Foreground="White" Foreground="White"
Margin="0 0 5 0" Margin="0 0 5 0"
@ -64,12 +70,167 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ModernRounded3DButtonStyle}"/> Style="{StaticResource ModernRounded3DButtonStyle}"/>
<ui:Button x:Name="DebugCloseButton" Content="Close" Appearance="Danger" Click="CloseApp" Width="200" Height="75" VerticalAlignment="Bottom" Margin="0 0 5 0" />
<ui:Button Content="Pictures" Click="SetVisibilityPicturePanel" Width="200" Height="75"
VerticalAlignment="Bottom" Margin="0 0 5 0" />
<ui:Button Content="Close" Appearance="Danger" Click="CloseApp" Width="200" Height="75" VerticalAlignment="Bottom" Margin="0 0 5 0" />
</StackPanel> </StackPanel>
<!-- Picture Gallery Dock (bottom-right) -->
<Grid Grid.Row="0"
x:Name="PictureGalleryDock"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="20"
Panel.ZIndex="3"
Visibility="Hidden">
<ui:Button Content="&#xE7C3;"
FontFamily="Segoe MDL2 Assets"
FontSize="30"
Width="72"
Height="72"
Click="SetVisibilityPicturePanel"
Background="#D4AF37"
Foreground="#1F1A00"
BorderBrush="#F6E7A1"
BorderThickness="2" />
<!-- Badge for new photos -->
<Border x:Name="NewPhotosBadge"
Background="#E61A1A1A"
BorderBrush="#FF0000"
BorderThickness="2"
CornerRadius="12"
Width="36"
Height="36"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,-8,-8,0"
Visibility="Collapsed"
Panel.ZIndex="1">
<TextBlock x:Name="NewPhotoCountText"
Text="1"
Foreground="White"
FontSize="18"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</Grid>
<!-- Gallery Prompt -->
<Border Grid.Row="0"
x:Name="GalleryPrompt"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="20"
Padding="16"
Background="#E61A1A1A"
BorderBrush="#66FFFFFF"
BorderThickness="1"
CornerRadius="12"
Panel.ZIndex="4"
Visibility="Collapsed">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="Foto gespeichert"
Foreground="White"
FontSize="24"
FontWeight="SemiBold"
VerticalAlignment="Center"
Margin="0 0 12 0" />
<ui:Button Content="Jetzt in Galerie ansehen"
Click="OpenGalleryFromPrompt"
Width="240"
Height="52" />
</StackPanel>
</Border>
<!-- Shutdown Slider (bottom-left) -->
<Grid Grid.Row="0"
x:Name="ShutdownDock"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Margin="20"
Panel.ZIndex="3"
Visibility="Hidden">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:Button Grid.Column="0"
x:Name="ShutdownToggleButton"
Content="&#xE7E8;"
FontFamily="Segoe MDL2 Assets"
FontSize="28"
Width="64"
Height="64"
Appearance="Danger"
Click="ToggleShutdownSlider" />
<Border Grid.Column="1"
Width="160"
Height="64"
Margin="8 0 0 0"
CornerRadius="10"
Background="#44202020"
ClipToBounds="True">
<Grid RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<TranslateTransform x:Name="ShutdownSliderTransform" X="160" />
</Grid.RenderTransform>
<ui:Button x:Name="ShutdownConfirmButton"
Content="&#xE7E8;"
FontFamily="Segoe MDL2 Assets"
FontSize="24"
Appearance="Danger"
Width="160"
Height="64"
Click="ShutdownWindows" />
</Grid>
</Border>
</Grid>
<!-- Welcome Overlay -->
<Grid Grid.RowSpan="2"
x:Name="WelcomeOverlay"
Background="#CC000000"
Panel.ZIndex="10">
<Border Background="#E61A1A1A"
BorderBrush="#66FFFFFF"
BorderThickness="1"
CornerRadius="20"
Padding="48"
MaxWidth="900"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel HorizontalAlignment="Center">
<TextBlock Text="Willkommen bei CamBooth!"
Foreground="White"
FontSize="56"
FontWeight="Bold"
TextAlignment="Center"
Margin="0 0 0 20"/>
<TextBlock Text="In wenigen Sekunden bist du bereit: Mit Start aktivierst du die Kamera und kannst direkt loslegen."
Foreground="#FFE8E8E8"
FontSize="28"
TextAlignment="Center"
TextWrapping="Wrap"
Margin="0 0 0 16"/>
<ui:Button Click="StartExperience"
Width="480"
Height="100"
HorizontalAlignment="Center"
Foreground="#1F1A00"
Background="#FFFFD280"
Margin="0 12 0 0">
<TextBlock Text="Party starten"
FontSize="52"
FontWeight="Bold"
TextAlignment="Center"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</ui:Button>
</StackPanel>
</Border>
</Grid>
<!-- DebugFrame --> <!-- DebugFrame -->
<Frame Grid.Row="1" <Frame Grid.Row="1"
x:Name="DebugFrame" x:Name="DebugFrame"

View File

@ -1,5 +1,9 @@
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using CamBooth.App.Core.AppSettings; using CamBooth.App.Core.AppSettings;
using CamBooth.App.Core.Logging; using CamBooth.App.Core.Logging;
@ -29,6 +33,24 @@ public partial class MainWindow : Window
private LiveViewPage? _liveViewPage; private LiveViewPage? _liveViewPage;
private bool _isPhotoProcessRunning;
private bool _isCameraStarted;
private bool _isShutdownSliderOpen;
private const string ShutdownGlyphClosed = "\uE7E8";
private const string ShutdownGlyphOpen = "\uE711";
private const double ShutdownSliderOffset = 160;
private readonly DispatcherTimer _focusStatusAnimationTimer = new() { Interval = TimeSpan.FromMilliseconds(250) };
private readonly DispatcherTimer _galleryPromptTimer = new() { Interval = TimeSpan.FromSeconds(5) };
private int _focusStatusDots;
public MainWindow( public MainWindow(
Logger logger, Logger logger,
@ -41,11 +63,24 @@ public partial class MainWindow : Window
this._pictureGalleryService = pictureGalleryService; this._pictureGalleryService = pictureGalleryService;
this._cameraService = cameraService; this._cameraService = cameraService;
InitializeComponent(); InitializeComponent();
this.SetVisibilityDebugConsole(this._isDebugConsoleVisible); this.SetVisibilityDebugConsole(_appSettings.IsDebugConsoleVisible);
this.SetVisibilityPicturePanel(this._isPicturePanelVisible); this.SetVisibilityPicturePanel(this._isPicturePanelVisible);
_ = this._pictureGalleryService.LoadThumbnailsToCache(); _ = this._pictureGalleryService.LoadThumbnailsToCache();
this.Closing += OnClosing; this.Closing += OnClosing;
TimerControlRectangleAnimation.OnTimerEllapsed += TimerControlRectangleAnimation_OnTimerEllapsed; TimerControlRectangleAnimation.OnTimerEllapsed += TimerControlRectangleAnimation_OnTimerEllapsed;
this._focusStatusAnimationTimer.Tick += (_, _) =>
{
this._focusStatusDots = (this._focusStatusDots + 1) % 4;
this.CaptureStatusText.Text = $"Scharfstellen{new string('.', this._focusStatusDots)}";
};
this._galleryPromptTimer.Tick += (_, _) => this.HideGalleryPrompt();
// Subscribe to new photo count changes
this._pictureGalleryService.NewPhotoCountChanged += PictureGalleryService_NewPhotoCountChanged;
this.DebugCloseButton.Visibility = Visibility.Collapsed;
this.HideDebugButton.Visibility = this._appSettings.IsDebugConsoleVisible ? Visibility.Visible : Visibility.Collapsed;
logger.Info($"config file loaded: '{appSettings.ConfigFileName}'"); logger.Info($"config file loaded: '{appSettings.ConfigFileName}'");
logger.Info("MainWindow initialized"); logger.Info("MainWindow initialized");
} }
@ -53,9 +88,12 @@ public partial class MainWindow : Window
private void TimerControlRectangleAnimation_OnTimerEllapsed() private void TimerControlRectangleAnimation_OnTimerEllapsed()
{ {
var photoTakenSuccessfully = false;
try try
{ {
this._cameraService.TakePhoto(); this._cameraService.TakePhoto();
photoTakenSuccessfully = true;
} }
catch (Exception exception) catch (Exception exception)
{ {
@ -65,7 +103,14 @@ public partial class MainWindow : Window
} }
finally finally
{ {
this.StopFocusStatusAnimation();
this.CaptureStatusText.Visibility = Visibility.Collapsed;
SwitchButtonAndTimerPanel(); SwitchButtonAndTimerPanel();
this._isPhotoProcessRunning = false;
if (photoTakenSuccessfully)
{
this.ShowGalleryPrompt();
}
} }
} }
@ -74,7 +119,10 @@ public partial class MainWindow : Window
{ {
if (visibility) if (visibility)
{ {
this.HideGalleryPrompt();
this.PicturePanel.Navigate(new PictureGalleryPage(this._appSettings, this._logger, this._pictureGalleryService)); this.PicturePanel.Navigate(new PictureGalleryPage(this._appSettings, this._logger, this._pictureGalleryService));
// Reset new photo count when opening gallery
this._pictureGalleryService.ResetNewPhotoCount();
} }
else else
{ {
@ -85,16 +133,47 @@ public partial class MainWindow : Window
} }
private void ClosePicturePanel()
{
if (this.PicturePanel.Content is PictureGalleryPage pictureGalleryPage)
{
pictureGalleryPage.CloseOpenDialog();
}
if (this.PicturePanel.Content is null)
{
return;
}
this.SetVisibilityPicturePanel(false);
}
private void OnClosing(object? sender, CancelEventArgs e) private void OnClosing(object? sender, CancelEventArgs e)
{ {
this._liveViewPage?.Dispose(); this._liveViewPage?.Dispose();
} }
private void NavToLiveView(object sender, RoutedEventArgs e) private void StartExperience(object sender, RoutedEventArgs e)
{ {
_liveViewPage = new LiveViewPage(this._logger, this._appSettings, this._cameraService); this.StartLiveViewIfNeeded();
MainFrame.Navigate(this._liveViewPage); this.WelcomeOverlay.Visibility = Visibility.Collapsed;
this.ButtonPanel.Visibility = Visibility.Visible;
this.PictureGalleryDock.Visibility = Visibility.Visible;
this.ShutdownDock.Visibility = Visibility.Visible;
}
private void StartLiveViewIfNeeded()
{
if (this._isCameraStarted)
{
return;
}
this._liveViewPage = new LiveViewPage(this._logger, this._appSettings, this._cameraService);
this.MainFrame.Navigate(this._liveViewPage);
this._isCameraStarted = true;
} }
@ -106,6 +185,11 @@ public partial class MainWindow : Window
private void SetVisibilityDebugConsole(bool visibility) private void SetVisibilityDebugConsole(bool visibility)
{ {
if (!_appSettings.IsDebugConsoleVisible)
{
return;
}
if (visibility) if (visibility)
{ {
this.DebugFrame.Navigate(new DebugConsolePage(this._logger)); this.DebugFrame.Navigate(new DebugConsolePage(this._logger));
@ -119,19 +203,44 @@ public partial class MainWindow : Window
} }
private void StartTakePhotoProcess(object sender, RoutedEventArgs e) private async void StartTakePhotoProcess(object sender, RoutedEventArgs e)
{ {
this.HideGalleryPrompt();
this.ClosePicturePanel();
if (this._isPhotoProcessRunning)
{
return;
}
this._isPhotoProcessRunning = true;
try try
{ {
#if DEBUG
TimerControlRectangleAnimation.StartTimer(1);
#else
TimerControlRectangleAnimation.StartTimer(5);
#endif
SwitchButtonAndTimerPanel(); SwitchButtonAndTimerPanel();
TimerControlRectangleAnimation.StartTimer(this._appSettings.PhotoCountdownSeconds);
this.StartFocusStatusAnimation();
this.CaptureStatusText.Visibility = Visibility.Visible;
await Task.Delay(TimeSpan.FromSeconds(this._appSettings.FocusDelaySeconds));
if (!this._isPhotoProcessRunning)
{
return;
}
await this._cameraService.PrepareFocusAsync(focusTimeoutMs: this._appSettings.FocusTimeoutMs);
this.StopFocusStatusAnimation();
this.CaptureStatusText.Visibility = Visibility.Collapsed;
} }
catch (Exception exception) catch (Exception exception)
{ {
this._isPhotoProcessRunning = false;
this.StopFocusStatusAnimation();
this.CaptureStatusText.Visibility = Visibility.Collapsed;
if (this.TimerPanel.Visibility == Visibility.Visible)
{
SwitchButtonAndTimerPanel();
}
this._logger.Error(exception.Message); this._logger.Error(exception.Message);
} }
} }
@ -139,6 +248,7 @@ public partial class MainWindow : Window
private void SwitchButtonAndTimerPanel() private void SwitchButtonAndTimerPanel()
{ {
this.ButtonPanel.Visibility = this.ButtonPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden; this.ButtonPanel.Visibility = this.ButtonPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden;
this.PictureGalleryDock.Visibility = this.PictureGalleryDock.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden;
this.TimerPanel.Visibility = this.TimerPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden; this.TimerPanel.Visibility = this.TimerPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden;
} }
@ -152,4 +262,92 @@ public partial class MainWindow : Window
{ {
this.Close(); this.Close();
} }
private void ShutdownWindows(object sender, RoutedEventArgs e)
{
if (this._appSettings.IsShutdownEnabled)
{
try
{
Process.Start(new ProcessStartInfo
{
FileName = "shutdown",
Arguments = "/s /t 0",
CreateNoWindow = true,
UseShellExecute = false
});
}
catch (Exception exception)
{
this._logger.Error(exception.Message);
System.Windows.MessageBox.Show("Windows konnte nicht heruntergefahren werden. Bitte erneut versuchen.");
}
}
this.Close();
}
private void ToggleShutdownSlider(object sender, RoutedEventArgs e)
{
this._isShutdownSliderOpen = !this._isShutdownSliderOpen;
this.ShutdownToggleButton.Content = this._isShutdownSliderOpen ? ShutdownGlyphOpen : ShutdownGlyphClosed;
this.AnimateShutdownSlider(this._isShutdownSliderOpen);
}
private void AnimateShutdownSlider(bool open)
{
var animation = new DoubleAnimation
{
To = open ? 0 : ShutdownSliderOffset,
Duration = TimeSpan.FromMilliseconds(250),
EasingFunction = new QuadraticEase()
};
this.ShutdownSliderTransform.BeginAnimation(System.Windows.Media.TranslateTransform.XProperty, animation);
}
private void StartFocusStatusAnimation()
{
this._focusStatusDots = 0;
this.CaptureStatusText.Text = "Scharfstellen";
this._focusStatusAnimationTimer.Start();
}
private void StopFocusStatusAnimation()
{
this._focusStatusAnimationTimer.Stop();
this._focusStatusDots = 0;
}
private void ShowGalleryPrompt()
{
this.GalleryPrompt.Visibility = Visibility.Visible;
this._galleryPromptTimer.Stop();
this._galleryPromptTimer.Start();
}
private void HideGalleryPrompt()
{
this._galleryPromptTimer.Stop();
this.GalleryPrompt.Visibility = Visibility.Collapsed;
}
private void OpenGalleryFromPrompt(object sender, RoutedEventArgs e)
{
this.HideGalleryPrompt();
this.SetVisibilityPicturePanel(true);
}
private void PictureGalleryService_NewPhotoCountChanged(object? sender, int newPhotoCount)
{
if (newPhotoCount > 0)
{
this.NewPhotosBadge.Visibility = Visibility.Visible;
this.NewPhotoCountText.Text = newPhotoCount.ToString();
}
else
{
this.NewPhotosBadge.Visibility = Visibility.Collapsed;
}
}
} }

View File

@ -0,0 +1,8 @@
- Rotate Flick Picture 180°
- Printer anschließen
- Galerie schließen
- Debug Window schließen
- Kiosk Modus einrichten
- Energiesparmodus abschalten
- Starbildschirm mit freundlicher Begrüßung, kurzer Erklärung, und viel Spaß wünschen mit der FotoCam
- Verschiedene Hinweise anzeigen beim Fotofrafieren (lächeln, Hasensohren, Zunge raus, Grimasse, usw.)

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -146,7 +146,14 @@ namespace EDSDKLib.API.Base
/// <exception cref="ArgumentNullException">The DownloadInfo is null</exception> /// <exception cref="ArgumentNullException">The DownloadInfo is null</exception>
public void DownloadFile(IDownloadInfo Info, string directory) public void DownloadFile(IDownloadInfo Info, string directory)
{ {
File.WriteAllBytes(Path.Combine(directory, Info.FileName), File.ReadAllBytes(this.mockImageFiles[new Random().Next(1, this.mockImageFiles.Count)].FullName)); if (this.mockImageFiles.Count == 0)
{
throw new InvalidOperationException("No mock images available");
}
Random random = new Random();
int randomIndex = random.Next(0, this.mockImageFiles.Count);
File.WriteAllBytes(Path.Combine(directory, Info.FileName), File.ReadAllBytes(this.mockImageFiles[randomIndex].FullName));
} }
/// <summary> /// <summary>
@ -200,7 +207,7 @@ namespace EDSDKLib.API.Base
this.DownloadReady?.Invoke(this, new DownloadInfoMock() this.DownloadReady?.Invoke(this, new DownloadInfoMock()
{ {
FileName = $"{mockImageFiles[random.Next(1, 99)].FullName}" FileName = $"MockPhoto_{Guid.NewGuid().ToString()}.jpg"
}); });
} }