Projektdateien hinzufügen.
This commit is contained in:
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
363
.gitignore
vendored
Normal file
363
.gitignore
vendored
Normal file
@@ -0,0 +1,363 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
3
MaxSlurper.slnx
Normal file
3
MaxSlurper.slnx
Normal file
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="MaxSlurper/MaxSlurper.csproj" />
|
||||
</Solution>
|
||||
29
MaxSlurper/App.xaml
Normal file
29
MaxSlurper/App.xaml
Normal file
@@ -0,0 +1,29 @@
|
||||
<Application x:Class="MaxSlurper.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:MaxSlurper"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- Wpf.Ui FluentUI theme resources -->
|
||||
<ui:ThemesDictionary Theme="Light" />
|
||||
<ui:ControlsDictionary />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Custom app-specific styles -->
|
||||
<Style TargetType="TextBlock" x:Key="HeaderText">
|
||||
<Setter Property="FontSize" Value="20" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TextBlock" x:Key="SubtleText">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
19
MaxSlurper/App.xaml.cs
Normal file
19
MaxSlurper/App.xaml.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using Wpf.Ui.Appearance;
|
||||
|
||||
namespace MaxSlurper
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : System.Windows.Application
|
||||
{
|
||||
protected override void OnStartup(System.Windows.StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
// The theme will be applied automatically when the window is shown
|
||||
}
|
||||
}
|
||||
}
|
||||
10
MaxSlurper/AssemblyInfo.cs
Normal file
10
MaxSlurper/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
7
MaxSlurper/Audio/IAudioPlayer.cs
Normal file
7
MaxSlurper/Audio/IAudioPlayer.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace MaxSlurper.Audio
|
||||
{
|
||||
public interface IAudioPlayer
|
||||
{
|
||||
void Play(string filename);
|
||||
}
|
||||
}
|
||||
22
MaxSlurper/Audio/SimpleAudioPlayer.cs
Normal file
22
MaxSlurper/Audio/SimpleAudioPlayer.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Media;
|
||||
using System.IO;
|
||||
|
||||
namespace MaxSlurper.Audio
|
||||
{
|
||||
public class SimpleAudioPlayer : IAudioPlayer
|
||||
{
|
||||
public void Play(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(System.AppContext.BaseDirectory, filename);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
using var player = new SoundPlayer(path);
|
||||
player.Play();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
193
MaxSlurper/Controls/ColorPickerWindow.xaml
Normal file
193
MaxSlurper/Controls/ColorPickerWindow.xaml
Normal file
@@ -0,0 +1,193 @@
|
||||
<ui:FluentWindow x:Class="MaxSlurper.Controls.ColorPickerWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
Title="Farbe aussuche"
|
||||
Height="500"
|
||||
Width="400"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Topmost="True"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowBackdropType="Mica"
|
||||
WindowCornerPreference="Round"
|
||||
Icon="/logo.ico">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar -->
|
||||
<ui:TitleBar Grid.Row="0"
|
||||
Title="Farbe aussuche"
|
||||
ShowMaximize="False"
|
||||
ShowMinimize="False"
|
||||
Icon="/logo.png" />
|
||||
|
||||
<!-- Color Preview -->
|
||||
<Border Grid.Row="1"
|
||||
Height="100"
|
||||
Background="{Binding SelectedColorBrush}"
|
||||
CornerRadius="12"
|
||||
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
Margin="20,8,20,16">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="12" ShadowDepth="2" Opacity="0.2"/>
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
|
||||
<!-- Color Values -->
|
||||
<StackPanel Grid.Row="2" Margin="20,0,20,16">
|
||||
<TextBlock Text="{Binding SelectedHex}"
|
||||
FontSize="22"
|
||||
FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Margin="0,0,0,4"/>
|
||||
<TextBlock Text="{Binding SelectedRgb}"
|
||||
FontSize="14"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Hue Slider -->
|
||||
<Grid Grid.Row="3" Margin="20,0,20,16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="Farbton"
|
||||
FontSize="13"
|
||||
FontWeight="Medium"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Margin="0,0,0,6"/>
|
||||
|
||||
<Border Grid.Row="1"
|
||||
Height="36"
|
||||
CornerRadius="8"
|
||||
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
||||
BorderThickness="1">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Color="#FF0000" Offset="0.0"/>
|
||||
<GradientStop Color="#FFFF00" Offset="0.167"/>
|
||||
<GradientStop Color="#00FF00" Offset="0.333"/>
|
||||
<GradientStop Color="#00FFFF" Offset="0.5"/>
|
||||
<GradientStop Color="#0000FF" Offset="0.667"/>
|
||||
<GradientStop Color="#FF00FF" Offset="0.833"/>
|
||||
<GradientStop Color="#FF0000" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
|
||||
<Canvas x:Name="HueCanvas"
|
||||
Background="Transparent"
|
||||
MouseLeftButtonDown="HueCanvas_MouseLeftButtonDown"
|
||||
MouseMove="HueCanvas_MouseMove"
|
||||
Cursor="Hand">
|
||||
<!-- Hue Indicator -->
|
||||
<Border x:Name="HueIndicator"
|
||||
Width="4"
|
||||
Height="36"
|
||||
Background="White"
|
||||
CornerRadius="2"
|
||||
IsHitTestVisible="False">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="4" ShadowDepth="0" Opacity="0.8" Color="Black"/>
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
</Canvas>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Saturation/Value Label -->
|
||||
<TextBlock Grid.Row="4"
|
||||
Text="Sättigung & Helligkeit"
|
||||
FontSize="13"
|
||||
FontWeight="Medium"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Margin="20,0,20,6"/>
|
||||
|
||||
<!-- Saturation/Value Picker -->
|
||||
<Border Grid.Row="5"
|
||||
CornerRadius="12"
|
||||
Margin="20,0,20,16"
|
||||
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
ClipToBounds="True"
|
||||
MinHeight="200">
|
||||
<Grid>
|
||||
<!-- Base hue color -->
|
||||
<Border x:Name="HueBackground" Background="Red"/>
|
||||
|
||||
<!-- White to transparent (Saturation) -->
|
||||
<Border>
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Color="#FFFFFFFF" Offset="0"/>
|
||||
<GradientStop Color="#00FFFFFF" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
</Border>
|
||||
|
||||
<!-- Transparent to black (Value) -->
|
||||
<Border>
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
|
||||
<GradientStop Color="#00000000" Offset="0"/>
|
||||
<GradientStop Color="#FF000000" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
</Border>
|
||||
|
||||
<Canvas x:Name="SvCanvas"
|
||||
Background="Transparent"
|
||||
MouseLeftButtonDown="SvCanvas_MouseLeftButtonDown"
|
||||
MouseMove="SvCanvas_MouseMove"
|
||||
Cursor="Cross">
|
||||
<!-- Selection Indicator -->
|
||||
<Ellipse x:Name="SvIndicator"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Stroke="White"
|
||||
StrokeThickness="3"
|
||||
Fill="Transparent"
|
||||
IsHitTestVisible="False">
|
||||
<Ellipse.Effect>
|
||||
<DropShadowEffect BlurRadius="6" ShadowDepth="0" Opacity="0.8" Color="Black"/>
|
||||
</Ellipse.Effect>
|
||||
</Ellipse>
|
||||
</Canvas>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Buttons -->
|
||||
<Grid Grid.Row="6" Margin="20,0,20,20">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ui:Button Content="Lass ma"
|
||||
Height="44"
|
||||
Margin="0,0,8,0"
|
||||
Click="CancelButton_Click"
|
||||
Appearance="Secondary"/>
|
||||
|
||||
<ui:Button Grid.Column="1"
|
||||
Content="Kopieren & Tschüss"
|
||||
Height="44"
|
||||
Padding="24,0"
|
||||
Click="OkButton_Click"
|
||||
Appearance="Primary"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ui:FluentWindow>
|
||||
262
MaxSlurper/Controls/ColorPickerWindow.xaml.cs
Normal file
262
MaxSlurper/Controls/ColorPickerWindow.xaml.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Wpf.Ui.Controls;
|
||||
|
||||
namespace MaxSlurper.Controls
|
||||
{
|
||||
public partial class ColorPickerWindow : FluentWindow, INotifyPropertyChanged
|
||||
{
|
||||
private double _hue = 0;
|
||||
private double _saturation = 1;
|
||||
private double _value = 1;
|
||||
private bool _isDraggingHue = false;
|
||||
private bool _isDraggingSv = false;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private System.Windows.Media.Color _selectedColor = Colors.Red;
|
||||
public System.Windows.Media.Color SelectedColor
|
||||
{
|
||||
get => _selectedColor;
|
||||
set
|
||||
{
|
||||
_selectedColor = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(SelectedColorBrush));
|
||||
OnPropertyChanged(nameof(SelectedHex));
|
||||
OnPropertyChanged(nameof(SelectedRgb));
|
||||
}
|
||||
}
|
||||
|
||||
public SolidColorBrush SelectedColorBrush => new SolidColorBrush(SelectedColor);
|
||||
|
||||
public string SelectedHex => $"#{SelectedColor.R:X2}{SelectedColor.G:X2}{SelectedColor.B:X2}";
|
||||
|
||||
public string SelectedRgb => $"RGB({SelectedColor.R}, {SelectedColor.G}, {SelectedColor.B})";
|
||||
|
||||
public ColorPickerWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
Loaded += ColorPickerWindow_Loaded;
|
||||
}
|
||||
|
||||
public ColorPickerWindow(System.Windows.Media.Color initialColor) : this()
|
||||
{
|
||||
SelectedColor = initialColor;
|
||||
ColorToHsv(initialColor, out _hue, out _saturation, out _value);
|
||||
}
|
||||
|
||||
private void ColorPickerWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UpdateHueBackground();
|
||||
UpdateHueIndicatorPosition();
|
||||
UpdateSvIndicatorPosition();
|
||||
}
|
||||
|
||||
private void HueCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_isDraggingHue = true;
|
||||
HueCanvas.CaptureMouse();
|
||||
UpdateHueFromMouse(e.GetPosition(HueCanvas));
|
||||
}
|
||||
|
||||
private void HueCanvas_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
|
||||
{
|
||||
if (_isDraggingHue)
|
||||
{
|
||||
UpdateHueFromMouse(e.GetPosition(HueCanvas));
|
||||
}
|
||||
}
|
||||
|
||||
private void SvCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_isDraggingSv = true;
|
||||
SvCanvas.CaptureMouse();
|
||||
UpdateSvFromMouse(e.GetPosition(SvCanvas));
|
||||
}
|
||||
|
||||
private void SvCanvas_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
|
||||
{
|
||||
if (_isDraggingSv)
|
||||
{
|
||||
UpdateSvFromMouse(e.GetPosition(SvCanvas));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonUp(e);
|
||||
if (_isDraggingHue)
|
||||
{
|
||||
_isDraggingHue = false;
|
||||
HueCanvas.ReleaseMouseCapture();
|
||||
}
|
||||
if (_isDraggingSv)
|
||||
{
|
||||
_isDraggingSv = false;
|
||||
SvCanvas.ReleaseMouseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHueFromMouse(System.Windows.Point position)
|
||||
{
|
||||
double width = HueCanvas.ActualWidth;
|
||||
if (width <= 0) return;
|
||||
|
||||
_hue = Math.Clamp(position.X / width, 0, 1) * 360;
|
||||
UpdateHueBackground();
|
||||
UpdateHueIndicatorPosition();
|
||||
UpdateColorFromHsv();
|
||||
}
|
||||
|
||||
private void UpdateSvFromMouse(System.Windows.Point position)
|
||||
{
|
||||
double width = SvCanvas.ActualWidth;
|
||||
double height = SvCanvas.ActualHeight;
|
||||
if (width <= 0 || height <= 0) return;
|
||||
|
||||
_saturation = Math.Clamp(position.X / width, 0, 1);
|
||||
_value = 1 - Math.Clamp(position.Y / height, 0, 1);
|
||||
|
||||
UpdateSvIndicatorPosition();
|
||||
UpdateColorFromHsv();
|
||||
}
|
||||
|
||||
private void UpdateHueBackground()
|
||||
{
|
||||
var hueColor = HsvToColor(_hue, 1, 1);
|
||||
HueBackground.Background = new SolidColorBrush(hueColor);
|
||||
}
|
||||
|
||||
private void UpdateHueIndicatorPosition()
|
||||
{
|
||||
if (HueCanvas.ActualWidth <= 0) return;
|
||||
|
||||
double x = (_hue / 360.0) * HueCanvas.ActualWidth;
|
||||
Canvas.SetLeft(HueIndicator, x - 2); // Center the 4px wide indicator
|
||||
}
|
||||
|
||||
private void UpdateSvIndicatorPosition()
|
||||
{
|
||||
if (SvCanvas.ActualWidth <= 0 || SvCanvas.ActualHeight <= 0) return;
|
||||
|
||||
double x = _saturation * SvCanvas.ActualWidth;
|
||||
double y = (1 - _value) * SvCanvas.ActualHeight;
|
||||
|
||||
Canvas.SetLeft(SvIndicator, x - 10); // Changed from 8 to 10 (half of 20)
|
||||
Canvas.SetTop(SvIndicator, y - 10); // Changed from 8 to 10 (half of 20)
|
||||
}
|
||||
|
||||
private void UpdateColorFromHsv()
|
||||
{
|
||||
SelectedColor = HsvToColor(_hue, _saturation, _value);
|
||||
}
|
||||
|
||||
private static System.Windows.Media.Color HsvToColor(double h, double s, double v)
|
||||
{
|
||||
double c = v * s;
|
||||
double x = c * (1 - Math.Abs((h / 60) % 2 - 1));
|
||||
double m = v - c;
|
||||
|
||||
double r = 0, g = 0, b = 0;
|
||||
|
||||
if (h >= 0 && h < 60)
|
||||
{
|
||||
r = c; g = x; b = 0;
|
||||
}
|
||||
else if (h >= 60 && h < 120)
|
||||
{
|
||||
r = x; g = c; b = 0;
|
||||
}
|
||||
else if (h >= 120 && h < 180)
|
||||
{
|
||||
r = 0; g = c; b = x;
|
||||
}
|
||||
else if (h >= 180 && h < 240)
|
||||
{
|
||||
r = 0; g = x; b = c;
|
||||
}
|
||||
else if (h >= 240 && h < 300)
|
||||
{
|
||||
r = x; g = 0; b = c;
|
||||
}
|
||||
else if (h >= 300 && h < 360)
|
||||
{
|
||||
r = c; g = 0; b = x;
|
||||
}
|
||||
|
||||
return System.Windows.Media.Color.FromRgb(
|
||||
(byte)Math.Round((r + m) * 255),
|
||||
(byte)Math.Round((g + m) * 255),
|
||||
(byte)Math.Round((b + m) * 255)
|
||||
);
|
||||
}
|
||||
|
||||
private static void ColorToHsv(System.Windows.Media.Color color, out double h, out double s, out double v)
|
||||
{
|
||||
double r = color.R / 255.0;
|
||||
double g = color.G / 255.0;
|
||||
double b = color.B / 255.0;
|
||||
|
||||
double max = Math.Max(r, Math.Max(g, b));
|
||||
double min = Math.Min(r, Math.Min(g, b));
|
||||
double delta = max - min;
|
||||
|
||||
// Hue
|
||||
if (delta == 0)
|
||||
{
|
||||
h = 0;
|
||||
}
|
||||
else if (max == r)
|
||||
{
|
||||
h = 60 * (((g - b) / delta) % 6);
|
||||
}
|
||||
else if (max == g)
|
||||
{
|
||||
h = 60 * (((b - r) / delta) + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
h = 60 * (((r - g) / delta) + 4);
|
||||
}
|
||||
|
||||
if (h < 0) h += 360;
|
||||
|
||||
// Saturation
|
||||
s = max == 0 ? 0 : delta / max;
|
||||
|
||||
// Value
|
||||
v = max;
|
||||
}
|
||||
|
||||
private void OkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
168
MaxSlurper/Controls/SettingsWindow.xaml
Normal file
168
MaxSlurper/Controls/SettingsWindow.xaml
Normal file
@@ -0,0 +1,168 @@
|
||||
<ui:FluentWindow x:Class="MaxSlurper.Controls.SettingsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
Title="Einstellungen"
|
||||
Height="520"
|
||||
Width="400"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Topmost="False"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowBackdropType="Mica"
|
||||
WindowCornerPreference="Round"
|
||||
Icon="/logo.ico">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar -->
|
||||
<ui:TitleBar Grid.Row="0"
|
||||
Title="Einstellungen"
|
||||
ShowMaximize="False"
|
||||
ShowMinimize="False"
|
||||
Icon="/logo.png" />
|
||||
|
||||
<!-- Main content -->
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Margin="24,16,24,16">
|
||||
<StackPanel>
|
||||
|
||||
<!-- Hotkey Settings Card -->
|
||||
<ui:Card
|
||||
Margin="0,0,0,16"
|
||||
Padding="20">
|
||||
<StackPanel>
|
||||
<Grid Margin="0,0,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ui:SymbolIcon Symbol="Keyboard24"
|
||||
FontSize="24"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,12,0"/>
|
||||
|
||||
<StackPanel Grid.Column="1">
|
||||
<TextBlock Text="Tastenkombination"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
|
||||
<TextBlock
|
||||
TextWrapping="Wrap"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Margin="0,2,0,0"><Run Language="de-de" Text="H"/><Run Text="otkey zum Farben schlürfen"/></TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Aktuelle Tastenkombination"
|
||||
FontSize="13"
|
||||
FontWeight="Medium"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Margin="0,0,0,8"/>
|
||||
|
||||
<!-- Hotkey Display -->
|
||||
<Border Background="{DynamicResource ControlFillColorDefaultBrush}"
|
||||
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12"
|
||||
Margin="0,0,0,16">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{Binding CurrentHotkey}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
|
||||
|
||||
<ui:Button Grid.Column="1"
|
||||
Content="Ändern"
|
||||
Appearance="Secondary"
|
||||
Click="ChangeHotkey_Click"
|
||||
Padding="16,8"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Instructions -->
|
||||
</StackPanel>
|
||||
</ui:Card>
|
||||
|
||||
<!-- About Card -->
|
||||
<ui:Card Padding="20">
|
||||
<StackPanel>
|
||||
<Grid Margin="0,0,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ui:SymbolIcon Symbol="Info24"
|
||||
FontSize="24"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,12,0"/>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="Über MaxSlurper"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="DER ColorPicker für Max"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="13"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Margin="0,0,0,8"/>
|
||||
<TextBlock TextWrapping="Wrap"
|
||||
FontSize="13"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}">
|
||||
<Run Text="Gemacht mit "/>
|
||||
<Run Text="♥" Foreground="Red" FontSize="14"/>
|
||||
<Run Text=" von Mathias"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</ui:Card>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- Bottom buttons -->
|
||||
<Border Grid.Row="2"
|
||||
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,1,0,0"
|
||||
Padding="24,16">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="Änderungen werden direkt jespeichert"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
|
||||
|
||||
<ui:Button Grid.Column="1"
|
||||
Content="Zumachen"
|
||||
Height="36"
|
||||
Padding="24,0"
|
||||
Click="Close_Click"
|
||||
Appearance="Primary"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ui:FluentWindow>
|
||||
231
MaxSlurper/Controls/SettingsWindow.xaml.cs
Normal file
231
MaxSlurper/Controls/SettingsWindow.xaml.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using MaxSlurper.Models;
|
||||
using MaxSlurper.Services;
|
||||
using Wpf.Ui.Controls;
|
||||
|
||||
namespace MaxSlurper.Controls
|
||||
{
|
||||
public partial class SettingsWindow : FluentWindow, INotifyPropertyChanged
|
||||
{
|
||||
private readonly AppSettings _settings;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly Action<AppSettings> _onSettingsChanged;
|
||||
private bool _isCapturingHotkey = false;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private string _currentHotkey = "";
|
||||
public string CurrentHotkey
|
||||
{
|
||||
get => _currentHotkey;
|
||||
set
|
||||
{
|
||||
_currentHotkey = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsWindow(AppSettings settings, ISettingsService settingsService, Action<AppSettings> onSettingsChanged)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
|
||||
_settings = settings;
|
||||
_settingsService = settingsService;
|
||||
_onSettingsChanged = onSettingsChanged;
|
||||
|
||||
UpdateHotkeyDisplay();
|
||||
}
|
||||
|
||||
private void UpdateHotkeyDisplay()
|
||||
{
|
||||
CurrentHotkey = _settings.HotkeyDisplayText;
|
||||
}
|
||||
|
||||
private void ChangeHotkey_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isCapturingHotkey)
|
||||
return;
|
||||
|
||||
_isCapturingHotkey = true;
|
||||
CurrentHotkey = "Drück deine Tasten...";
|
||||
|
||||
var captureWindow = new HotkeyCapture();
|
||||
captureWindow.Owner = this;
|
||||
|
||||
if (captureWindow.ShowDialog() == true)
|
||||
{
|
||||
_settings.HotkeyModifiers = captureWindow.CapturedModifiers;
|
||||
_settings.HotkeyKey = captureWindow.CapturedKey;
|
||||
|
||||
UpdateHotkeyDisplay();
|
||||
SaveSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateHotkeyDisplay();
|
||||
}
|
||||
|
||||
_isCapturingHotkey = false;
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
_settingsService.SaveSettings(_settings);
|
||||
_onSettingsChanged(_settings);
|
||||
}
|
||||
|
||||
private void Close_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string? name = null) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
|
||||
// Hotkey capture dialog
|
||||
public partial class HotkeyCapture : FluentWindow
|
||||
{
|
||||
public ModifierKeys CapturedModifiers { get; private set; } = ModifierKeys.None;
|
||||
public Key CapturedKey { get; private set; } = Key.None;
|
||||
|
||||
public HotkeyCapture()
|
||||
{
|
||||
Title = "Tastenkombination aufnehmen";
|
||||
Width = 400;
|
||||
Height = 200;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
ResizeMode = ResizeMode.NoResize;
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
WindowBackdropType = WindowBackdropType.Mica;
|
||||
WindowCornerPreference = WindowCornerPreference.Round;
|
||||
|
||||
var grid = new Grid();
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
|
||||
var titleBar = new TitleBar
|
||||
{
|
||||
Title = "Tastenkombination",
|
||||
ShowMaximize = false,
|
||||
ShowMinimize = false
|
||||
};
|
||||
Grid.SetRow(titleBar, 0);
|
||||
grid.Children.Add(titleBar);
|
||||
|
||||
var stackPanel = new StackPanel
|
||||
{
|
||||
Margin = new Thickness(24),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
HorizontalAlignment = System.Windows.HorizontalAlignment.Center
|
||||
};
|
||||
Grid.SetRow(stackPanel, 1);
|
||||
|
||||
var infoText = new System.Windows.Controls.TextBlock
|
||||
{
|
||||
Text = "Drück die Tastenkombination, die de haben willst",
|
||||
FontSize = 14,
|
||||
TextAlignment = TextAlignment.Center,
|
||||
Foreground = (System.Windows.Media.Brush)FindResource("TextFillColorSecondaryBrush"),
|
||||
Margin = new Thickness(0, 0, 0, 16),
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
};
|
||||
stackPanel.Children.Add(infoText);
|
||||
|
||||
var keyDisplay = new System.Windows.Controls.TextBlock
|
||||
{
|
||||
Text = "Warte uff Eingabe...",
|
||||
FontSize = 18,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
TextAlignment = TextAlignment.Center,
|
||||
Foreground = (System.Windows.Media.Brush)FindResource("TextFillColorPrimaryBrush")
|
||||
};
|
||||
stackPanel.Children.Add(keyDisplay);
|
||||
|
||||
grid.Children.Add(stackPanel);
|
||||
|
||||
var buttonPanel = new StackPanel
|
||||
{
|
||||
Orientation = System.Windows.Controls.Orientation.Horizontal,
|
||||
HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
|
||||
Margin = new Thickness(24, 16, 24, 24)
|
||||
};
|
||||
Grid.SetRow(buttonPanel, 2);
|
||||
|
||||
var cancelButton = new Wpf.Ui.Controls.Button
|
||||
{
|
||||
Content = "Abbrechen",
|
||||
Appearance = ControlAppearance.Secondary,
|
||||
Margin = new Thickness(0, 0, 8, 0),
|
||||
Padding = new Thickness(16, 8, 16, 8)
|
||||
};
|
||||
cancelButton.Click += (s, e) => { DialogResult = false; Close(); };
|
||||
buttonPanel.Children.Add(cancelButton);
|
||||
|
||||
var okButton = new Wpf.Ui.Controls.Button
|
||||
{
|
||||
Content = "OK",
|
||||
Appearance = ControlAppearance.Primary,
|
||||
Padding = new Thickness(24, 8, 24, 8),
|
||||
IsEnabled = false
|
||||
};
|
||||
okButton.Click += (s, e) => { DialogResult = true; Close(); };
|
||||
buttonPanel.Children.Add(okButton);
|
||||
|
||||
grid.Children.Add(buttonPanel);
|
||||
|
||||
Content = grid;
|
||||
|
||||
// Capture keys
|
||||
PreviewKeyDown += (s, e) =>
|
||||
{
|
||||
e.Handled = true;
|
||||
|
||||
var modifiers = Keyboard.Modifiers;
|
||||
var key = e.Key == Key.System ? e.SystemKey : e.Key;
|
||||
|
||||
// Ignore modifier-only keys
|
||||
if (key == Key.LeftCtrl || key == Key.RightCtrl ||
|
||||
key == Key.LeftAlt || key == Key.RightAlt ||
|
||||
key == Key.LeftShift || key == Key.RightShift ||
|
||||
key == Key.LWin || key == Key.RWin)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Require at least one modifier
|
||||
if (modifiers == ModifierKeys.None)
|
||||
{
|
||||
keyDisplay.Text = "Mindestens ein Modifier (Strg, Alt, Shift, Win) musste drücken!";
|
||||
okButton.IsEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
CapturedModifiers = modifiers;
|
||||
CapturedKey = key;
|
||||
|
||||
var parts = new System.Collections.Generic.List<string>();
|
||||
if (modifiers.HasFlag(ModifierKeys.Control))
|
||||
parts.Add("Strg");
|
||||
if (modifiers.HasFlag(ModifierKeys.Alt))
|
||||
parts.Add("Alt");
|
||||
if (modifiers.HasFlag(ModifierKeys.Shift))
|
||||
parts.Add("Shift");
|
||||
if (modifiers.HasFlag(ModifierKeys.Windows))
|
||||
parts.Add("Win");
|
||||
parts.Add(key.ToString());
|
||||
|
||||
keyDisplay.Text = string.Join(" + ", parts);
|
||||
okButton.IsEnabled = true;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
180
MaxSlurper/MainWindow.xaml
Normal file
180
MaxSlurper/MainWindow.xaml
Normal file
@@ -0,0 +1,180 @@
|
||||
<ui:FluentWindow x:Class="MaxSlurper.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
Title="MaxSlurper"
|
||||
Height="600"
|
||||
Width="450"
|
||||
ResizeMode="NoResize"
|
||||
WindowBackdropType="Mica"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowCornerPreference="Round"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Icon="pack://application:,,,/logo.ico">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar with window controls -->
|
||||
<ui:TitleBar Grid.Row="0"
|
||||
Title="MaxSlurper"
|
||||
ShowMaximize="False"
|
||||
ShowMinimize="True">
|
||||
<ui:TitleBar.Icon>
|
||||
<ui:ImageIcon Source="pack://application:,,,/logo.png" />
|
||||
</ui:TitleBar.Icon>
|
||||
<ui:TitleBar.Header>
|
||||
<ui:Button Appearance="Secondary"
|
||||
Icon="{ui:SymbolIcon Settings24}"
|
||||
ToolTip="Einstellungen"
|
||||
Command="{Binding OpenSettingsCommand}"
|
||||
Padding="8"
|
||||
Margin="0,0,8,0"/>
|
||||
</ui:TitleBar.Header>
|
||||
</ui:TitleBar>
|
||||
|
||||
<!-- Main content -->
|
||||
<Grid Grid.Row="1" Margin="16,8,16,16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header: color preview + actions -->
|
||||
<ui:Card Grid.Row="0" Margin="0,0,0,16" Padding="16">
|
||||
<DockPanel LastChildFill="False">
|
||||
<!-- Color preview -->
|
||||
<Border Width="72"
|
||||
Height="72"
|
||||
Background="{Binding SelectedColorBrush}"
|
||||
CornerRadius="12"
|
||||
DockPanel.Dock="Left"
|
||||
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
Cursor="Hand"
|
||||
ToolTip="Drück ma druff, dann kannste dir 'ne Farbe raussuche">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="8"
|
||||
ShadowDepth="2"
|
||||
Opacity="0.15"
|
||||
Color="{Binding SelectedColorBrush.Color}" />
|
||||
</Border.Effect>
|
||||
<Border.InputBindings>
|
||||
<MouseBinding MouseAction="LeftClick"
|
||||
Command="{Binding OpenColorPickerCommand}" />
|
||||
</Border.InputBindings>
|
||||
</Border>
|
||||
|
||||
<!-- Details -->
|
||||
<StackPanel Orientation="Vertical"
|
||||
Margin="16,0,16,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="Zuletzt jeklaut"
|
||||
Style="{StaticResource SubtleText}"
|
||||
Margin="0,0,0,4" />
|
||||
<TextBlock Text="{Binding SelectedHex}"
|
||||
FontWeight="SemiBold"
|
||||
FontSize="20"
|
||||
Margin="0,0,0,4"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<TextBlock Text="{Binding SelectedRgb}"
|
||||
Style="{StaticResource SubtleText}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Actions -->
|
||||
<StackPanel Orientation="Vertical"
|
||||
DockPanel.Dock="Right">
|
||||
<ui:Button Content="Farbe schlürfen"
|
||||
Appearance="Primary"
|
||||
Icon="{ui:SymbolIcon Eyedropper24}"
|
||||
Command="{Binding PickColorCommand}"
|
||||
Padding="16,8"
|
||||
Width="160" />
|
||||
<ui:Button Content="HEX kopieren"
|
||||
Appearance="Secondary"
|
||||
Icon="{ui:SymbolIcon Copy24}"
|
||||
Command="{Binding CopyHexCommand}"
|
||||
Padding="16,8"
|
||||
Width="160"
|
||||
Margin="0,8,0,0" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</ui:Card>
|
||||
|
||||
<!-- History list -->
|
||||
<ui:Card Grid.Row="1" Margin="0,0,0,0" Padding="16">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,12">
|
||||
<TextBlock Text="Wat ick schon so jeklaut hab"
|
||||
Style="{StaticResource HeaderText}" />
|
||||
<TextBlock Text="Druff klicken und ab dafuer, wa"
|
||||
Style="{StaticResource SubtleText}"
|
||||
Margin="0,4,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<ItemsControl ItemsSource="{Binding History}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ui:CardControl Margin="0,0,0,8"
|
||||
Padding="8"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left">
|
||||
<ui:CardControl.InputBindings>
|
||||
<MouseBinding MouseAction="LeftClick"
|
||||
Command="{Binding DataContext.CopyHexCommand, RelativeSource={RelativeSource AncestorType=ui:FluentWindow}}"
|
||||
CommandParameter="{Binding}" />
|
||||
</ui:CardControl.InputBindings>
|
||||
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Color preview on the left -->
|
||||
<Border Width="48"
|
||||
Height="48"
|
||||
Background="{Binding Brush}"
|
||||
CornerRadius="8"
|
||||
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<!-- Text info in the middle -->
|
||||
<StackPanel Grid.Column="1"
|
||||
Margin="12,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding Hex}"
|
||||
FontWeight="Medium"
|
||||
FontSize="15"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<TextBlock Text="{Binding Rgb}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Margin="0,2,0,0" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ui:CardControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</ui:Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ui:FluentWindow>
|
||||
111
MaxSlurper/MainWindow.xaml.cs
Normal file
111
MaxSlurper/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using MaxSlurper.Services;
|
||||
using MaxSlurper.Audio;
|
||||
using MaxSlurper.ViewModels;
|
||||
using Wpf.Ui.Controls;
|
||||
using Wpf.Ui.Appearance;
|
||||
|
||||
namespace MaxSlurper
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : FluentWindow
|
||||
{
|
||||
private NotifyIcon _notifyIcon;
|
||||
private readonly MainViewModel _vm;
|
||||
private readonly IHotkeyService _hotkeyService;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Apply system theme and enable automatic theme switching
|
||||
SystemThemeWatcher.Watch(this);
|
||||
|
||||
var colorPicker = new Win32ColorPickerService();
|
||||
var audio = new SimpleAudioPlayer();
|
||||
var settingsService = new SettingsService();
|
||||
_hotkeyService = new HotkeyService();
|
||||
|
||||
_vm = new MainViewModel(colorPicker, audio, settingsService);
|
||||
DataContext = _vm;
|
||||
|
||||
// Handle settings changes to update hotkey
|
||||
_vm.OnSettingsChanged = (settings) =>
|
||||
{
|
||||
RegisterHotkey(settings);
|
||||
};
|
||||
|
||||
SetupTrayIcon();
|
||||
|
||||
// Register initial hotkey after window is loaded
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
RegisterHotkey(_vm.Settings);
|
||||
};
|
||||
}
|
||||
|
||||
private void RegisterHotkey(Models.AppSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
_hotkeyService.RegisterHotkey(
|
||||
settings.HotkeyModifiers,
|
||||
settings.HotkeyKey,
|
||||
() =>
|
||||
{
|
||||
// Execute on UI thread
|
||||
Dispatcher.BeginInvoke(new Action(() => _vm.PickColorCommand.Execute(null)));
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Hotkey registration failed (might be already in use)
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTrayIcon()
|
||||
{
|
||||
_notifyIcon = new NotifyIcon();
|
||||
_notifyIcon.Icon = System.Drawing.Icon.ExtractAssociatedIcon(System.Reflection.Assembly.GetEntryAssembly().Location);
|
||||
_notifyIcon.Visible = true;
|
||||
_notifyIcon.Text = "MaxSlurper";
|
||||
|
||||
var contextMenu = new ContextMenuStrip();
|
||||
var pickItem = new ToolStripMenuItem("Farbe schlürfen");
|
||||
// Use BeginInvoke to avoid synchronous invocation while windows may be closing
|
||||
pickItem.Click += (s, e) => { Dispatcher.BeginInvoke(new Action(() => _vm.PickColorCommand.Execute(null))); };
|
||||
var openItem = new ToolStripMenuItem("Fenster uffmachen");
|
||||
openItem.Click += (s, e) => { Dispatcher.BeginInvoke(new Action(() => { Show(); WindowState = WindowState.Normal; Activate(); })); };
|
||||
var settingsItem = new ToolStripMenuItem("Einstellungen");
|
||||
settingsItem.Click += (s, e) => { Dispatcher.BeginInvoke(new Action(() => { Show(); WindowState = WindowState.Normal; Activate(); _vm.OpenSettingsCommand.Execute(null); })); };
|
||||
var exitItem = new ToolStripMenuItem("Tschüss");
|
||||
exitItem.Click += (s, e) => { _notifyIcon.Visible = false; System.Windows.Application.Current.Shutdown(); };
|
||||
contextMenu.Items.Add(pickItem);
|
||||
contextMenu.Items.Add(openItem);
|
||||
contextMenu.Items.Add(settingsItem);
|
||||
contextMenu.Items.Add(new ToolStripSeparator());
|
||||
contextMenu.Items.Add(exitItem);
|
||||
_notifyIcon.ContextMenuStrip = contextMenu;
|
||||
_notifyIcon.DoubleClick += (s, e) => { Dispatcher.BeginInvoke(new Action(() => { Show(); WindowState = WindowState.Normal; Activate(); })); };
|
||||
}
|
||||
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
Hide();
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
_hotkeyService.UnregisterHotkey();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
MaxSlurper/MaxSlurper.csproj
Normal file
53
MaxSlurper/MaxSlurper.csproj
Normal file
@@ -0,0 +1,53 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<Authors>Mathias Wagner</Authors>
|
||||
<Company>MaxSlurper</Company>
|
||||
<Product>MaxSlurper</Product>
|
||||
<Description>Weil Max danach gefragt hat</Description>
|
||||
<Copyright>Copyright © 2025</Copyright>
|
||||
<ApplicationIcon>logo.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Publishing settings for single-file deployment -->
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<DebugType>embedded</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<TrimMode>none</TrimMode>
|
||||
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- ModernWpf removed to revert to standard WPF resources -->
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include the provided Slurp.wav (located at the solution root) and copy it to output so it can be played at runtime -->
|
||||
<ItemGroup>
|
||||
<Content Include="..\Slurp.wav">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include logo files -->
|
||||
<ItemGroup>
|
||||
<Resource Include="logo.ico" />
|
||||
<Resource Include="logo.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WPF-UI" Version="3.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
59
MaxSlurper/Models/AppSettings.cs
Normal file
59
MaxSlurper/Models/AppSettings.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MaxSlurper.Models
|
||||
{
|
||||
public class AppSettings : INotifyPropertyChanged
|
||||
{
|
||||
private ModifierKeys _hotkeyModifiers = ModifierKeys.Control | ModifierKeys.Shift;
|
||||
private Key _hotkeyKey = Key.C;
|
||||
|
||||
public ModifierKeys HotkeyModifiers
|
||||
{
|
||||
get => _hotkeyModifiers;
|
||||
set
|
||||
{
|
||||
_hotkeyModifiers = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(HotkeyDisplayText));
|
||||
}
|
||||
}
|
||||
|
||||
public Key HotkeyKey
|
||||
{
|
||||
get => _hotkeyKey;
|
||||
set
|
||||
{
|
||||
_hotkeyKey = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(HotkeyDisplayText));
|
||||
}
|
||||
}
|
||||
|
||||
public string HotkeyDisplayText
|
||||
{
|
||||
get
|
||||
{
|
||||
var parts = new System.Collections.Generic.List<string>();
|
||||
|
||||
if (HotkeyModifiers.HasFlag(ModifierKeys.Control))
|
||||
parts.Add("Strg");
|
||||
if (HotkeyModifiers.HasFlag(ModifierKeys.Alt))
|
||||
parts.Add("Alt");
|
||||
if (HotkeyModifiers.HasFlag(ModifierKeys.Shift))
|
||||
parts.Add("Shift");
|
||||
if (HotkeyModifiers.HasFlag(ModifierKeys.Windows))
|
||||
parts.Add("Win");
|
||||
|
||||
parts.Add(HotkeyKey.ToString());
|
||||
|
||||
return string.Join(" + ", parts);
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
protected void OnPropertyChanged([CallerMemberName] string? name = null) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
}
|
||||
21
MaxSlurper/Models/ColorItem.cs
Normal file
21
MaxSlurper/Models/ColorItem.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MaxSlurper.Models
|
||||
{
|
||||
public class ColorItem
|
||||
{
|
||||
public byte R { get; set; }
|
||||
public byte G { get; set; }
|
||||
public byte B { get; set; }
|
||||
public string Hex { get; set; } = "#FFFFFF";
|
||||
public string Rgb { get; set; } = "255,255,255";
|
||||
public SolidColorBrush Brush { get; set; } = new SolidColorBrush(Colors.White);
|
||||
|
||||
public void UpdateDerived()
|
||||
{
|
||||
Hex = $"#{R:X2}{G:X2}{B:X2}";
|
||||
Rgb = $"{R},{G},{B}";
|
||||
Brush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(R, G, B));
|
||||
}
|
||||
}
|
||||
}
|
||||
20
MaxSlurper/RelayCommand.cs
Normal file
20
MaxSlurper/RelayCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MaxSlurper
|
||||
{
|
||||
public class RelayCommand : ICommand
|
||||
{
|
||||
private readonly Action<object?> _execute;
|
||||
private readonly Func<object?, bool>? _canExecute;
|
||||
public RelayCommand(Action<object?> execute, Func<object?, bool>? canExecute = null)
|
||||
{
|
||||
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
public bool CanExecute(object? parameter) => _canExecute?.Invoke(parameter) ?? true;
|
||||
public void Execute(object? parameter) => _execute(parameter);
|
||||
public event EventHandler? CanExecuteChanged;
|
||||
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
92
MaxSlurper/Services/HotkeyService.cs
Normal file
92
MaxSlurper/Services/HotkeyService.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace MaxSlurper.Services
|
||||
{
|
||||
public interface IHotkeyService
|
||||
{
|
||||
bool RegisterHotkey(ModifierKeys modifiers, Key key, Action callback);
|
||||
void UnregisterHotkey();
|
||||
}
|
||||
|
||||
public class HotkeyService : IHotkeyService
|
||||
{
|
||||
private const int WM_HOTKEY = 0x0312;
|
||||
private const int HOTKEY_ID = 9000;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
||||
|
||||
private IntPtr _windowHandle;
|
||||
private HwndSource? _source;
|
||||
private Action? _callback;
|
||||
|
||||
public bool RegisterHotkey(ModifierKeys modifiers, Key key, Action callback)
|
||||
{
|
||||
UnregisterHotkey();
|
||||
|
||||
_callback = callback;
|
||||
|
||||
// Get the main window handle
|
||||
var mainWindow = System.Windows.Application.Current.MainWindow;
|
||||
if (mainWindow == null)
|
||||
return false;
|
||||
|
||||
_windowHandle = new WindowInteropHelper(mainWindow).Handle;
|
||||
_source = HwndSource.FromHwnd(_windowHandle);
|
||||
|
||||
if (_source == null)
|
||||
return false;
|
||||
|
||||
_source.AddHook(HwndHook);
|
||||
|
||||
uint fsModifiers = 0;
|
||||
if (modifiers.HasFlag(ModifierKeys.Alt))
|
||||
fsModifiers |= 0x0001; // MOD_ALT
|
||||
if (modifiers.HasFlag(ModifierKeys.Control))
|
||||
fsModifiers |= 0x0002; // MOD_CONTROL
|
||||
if (modifiers.HasFlag(ModifierKeys.Shift))
|
||||
fsModifiers |= 0x0004; // MOD_SHIFT
|
||||
if (modifiers.HasFlag(ModifierKeys.Windows))
|
||||
fsModifiers |= 0x0008; // MOD_WIN
|
||||
|
||||
uint vk = (uint)KeyInterop.VirtualKeyFromKey(key);
|
||||
|
||||
return RegisterHotKey(_windowHandle, HOTKEY_ID, fsModifiers, vk);
|
||||
}
|
||||
|
||||
public void UnregisterHotkey()
|
||||
{
|
||||
if (_source != null)
|
||||
{
|
||||
_source.RemoveHook(HwndHook);
|
||||
_source = null;
|
||||
}
|
||||
|
||||
if (_windowHandle != IntPtr.Zero)
|
||||
{
|
||||
UnregisterHotKey(_windowHandle, HOTKEY_ID);
|
||||
_windowHandle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_callback = null;
|
||||
}
|
||||
|
||||
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
if (msg == WM_HOTKEY && wParam.ToInt32() == HOTKEY_ID)
|
||||
{
|
||||
_callback?.Invoke();
|
||||
handled = true;
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
MaxSlurper/Services/IColorPickerService.cs
Normal file
10
MaxSlurper/Services/IColorPickerService.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MaxSlurper.Services
|
||||
{
|
||||
public interface IColorPickerService
|
||||
{
|
||||
System.Threading.Tasks.Task<System.Windows.Media.Color?> PickColorAsync();
|
||||
}
|
||||
}
|
||||
103
MaxSlurper/Services/SettingsService.cs
Normal file
103
MaxSlurper/Services/SettingsService.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows.Input;
|
||||
using MaxSlurper.Models;
|
||||
|
||||
namespace MaxSlurper.Services
|
||||
{
|
||||
public interface ISettingsService
|
||||
{
|
||||
AppSettings LoadSettings();
|
||||
void SaveSettings(AppSettings settings);
|
||||
}
|
||||
|
||||
public class SettingsService : ISettingsService
|
||||
{
|
||||
private readonly string _settingsPath;
|
||||
|
||||
public SettingsService()
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var appFolder = Path.Combine(appData, "MaxSlurper");
|
||||
Directory.CreateDirectory(appFolder);
|
||||
_settingsPath = Path.Combine(appFolder, "settings.json");
|
||||
}
|
||||
|
||||
public AppSettings LoadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_settingsPath))
|
||||
{
|
||||
var json = File.ReadAllText(_settingsPath);
|
||||
var data = JsonSerializer.Deserialize<SettingsData>(json);
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
var settings = new AppSettings();
|
||||
|
||||
// Parse modifiers
|
||||
ModifierKeys modifiers = ModifierKeys.None;
|
||||
if (data.HotkeyModifiers != null)
|
||||
{
|
||||
if (data.HotkeyModifiers.Contains("Control"))
|
||||
modifiers |= ModifierKeys.Control;
|
||||
if (data.HotkeyModifiers.Contains("Alt"))
|
||||
modifiers |= ModifierKeys.Alt;
|
||||
if (data.HotkeyModifiers.Contains("Shift"))
|
||||
modifiers |= ModifierKeys.Shift;
|
||||
if (data.HotkeyModifiers.Contains("Windows"))
|
||||
modifiers |= ModifierKeys.Windows;
|
||||
}
|
||||
settings.HotkeyModifiers = modifiers;
|
||||
|
||||
// Parse key
|
||||
if (!string.IsNullOrEmpty(data.HotkeyKey) &&
|
||||
Enum.TryParse<Key>(data.HotkeyKey, out var key))
|
||||
{
|
||||
settings.HotkeyKey = key;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return new AppSettings();
|
||||
}
|
||||
|
||||
public void SaveSettings(AppSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var modifiers = new System.Collections.Generic.List<string>();
|
||||
if (settings.HotkeyModifiers.HasFlag(ModifierKeys.Control))
|
||||
modifiers.Add("Control");
|
||||
if (settings.HotkeyModifiers.HasFlag(ModifierKeys.Alt))
|
||||
modifiers.Add("Alt");
|
||||
if (settings.HotkeyModifiers.HasFlag(ModifierKeys.Shift))
|
||||
modifiers.Add("Shift");
|
||||
if (settings.HotkeyModifiers.HasFlag(ModifierKeys.Windows))
|
||||
modifiers.Add("Windows");
|
||||
|
||||
var data = new SettingsData
|
||||
{
|
||||
HotkeyModifiers = modifiers.ToArray(),
|
||||
HotkeyKey = settings.HotkeyKey.ToString()
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(_settingsPath, json);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private class SettingsData
|
||||
{
|
||||
public string[]? HotkeyModifiers { get; set; }
|
||||
public string? HotkeyKey { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
283
MaxSlurper/Services/Win32ColorPickerService.cs
Normal file
283
MaxSlurper/Services/Win32ColorPickerService.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Interop;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using SD = System.Drawing;
|
||||
|
||||
namespace MaxSlurper.Services
|
||||
{
|
||||
public class Win32ColorPickerService : IColorPickerService
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
private static extern System.IntPtr GetDC(System.IntPtr hwnd);
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int ReleaseDC(System.IntPtr hwnd, System.IntPtr hdc);
|
||||
[DllImport("gdi32.dll")]
|
||||
private static extern uint GetPixel(System.IntPtr hdc, int nXPos, int nYPos);
|
||||
[DllImport("gdi32.dll")]
|
||||
private static extern bool DeleteObject(IntPtr hObject);
|
||||
|
||||
public async Task<System.Windows.Media.Color?> PickColorAsync()
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var tcs = new TaskCompletionSource<System.Windows.Media.Color?>();
|
||||
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// If app is shutting down, abort
|
||||
if (System.Windows.Application.Current == null || System.Windows.Application.Current.Dispatcher.HasShutdownStarted || System.Windows.Application.Current.Dispatcher.HasShutdownFinished)
|
||||
{
|
||||
tcs.TrySetResult(null);
|
||||
return;
|
||||
}
|
||||
|
||||
bool finished = false; // guard to ensure cleanup runs once
|
||||
void Finish(System.Windows.Media.Color? result)
|
||||
{
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
try { tcs.TrySetResult(result); } catch { }
|
||||
}
|
||||
|
||||
// Create full-screen transparent overlay covering virtual screen (multi-monitor aware)
|
||||
var overlay = new System.Windows.Window
|
||||
{
|
||||
WindowStyle = WindowStyle.None,
|
||||
AllowsTransparency = true,
|
||||
// near-transparent background so the window is hit-testable but visually transparent
|
||||
Background = new SolidColorBrush(System.Windows.Media.Color.FromArgb(1,0,0,0)),
|
||||
Topmost = true,
|
||||
Left = SystemParameters.VirtualScreenLeft,
|
||||
Top = SystemParameters.VirtualScreenTop,
|
||||
Width = SystemParameters.VirtualScreenWidth,
|
||||
Height = SystemParameters.VirtualScreenHeight,
|
||||
ShowInTaskbar = false,
|
||||
ShowActivated = true
|
||||
};
|
||||
|
||||
// Magnifier settings - 17x17 grid for clearer center view
|
||||
const int sampleSize = 17; // pixels captured (square)
|
||||
const int magnifierSize = 238; // display size in px (17 * 14 = 238 for even pixel sizing)
|
||||
|
||||
var root = new System.Windows.Controls.Canvas();
|
||||
overlay.Content = root;
|
||||
|
||||
// Image that shows the magnified (pixelated) sample
|
||||
var magnifierImage = new System.Windows.Controls.Image
|
||||
{
|
||||
Width = magnifierSize,
|
||||
Height = magnifierSize,
|
||||
Stretch = Stretch.Fill,
|
||||
RenderTransformOrigin = new System.Windows.Point(0.5,0.5),
|
||||
ClipToBounds = true
|
||||
};
|
||||
|
||||
RenderOptions.SetBitmapScalingMode(magnifierImage, BitmapScalingMode.NearestNeighbor);
|
||||
|
||||
// Clip the image to rounded corners
|
||||
var imageClip = new RectangleGeometry
|
||||
{
|
||||
Rect = new Rect(0, 0, magnifierSize, magnifierSize),
|
||||
RadiusX = 12,
|
||||
RadiusY = 12
|
||||
};
|
||||
magnifierImage.Clip = imageClip;
|
||||
|
||||
// Outer border with modern, rounded styling
|
||||
var outerBorder = new Border
|
||||
{
|
||||
Width = magnifierSize + 16,
|
||||
Height = magnifierSize + 16,
|
||||
BorderBrush = new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 60, 60, 65)),
|
||||
BorderThickness = new Thickness(4),
|
||||
CornerRadius = new CornerRadius(16),
|
||||
Background = new SolidColorBrush(System.Windows.Media.Color.FromArgb(240, 20, 20, 24)),
|
||||
Padding = new Thickness(4),
|
||||
Effect = new System.Windows.Media.Effects.DropShadowEffect
|
||||
{
|
||||
BlurRadius = 30,
|
||||
ShadowDepth = 4,
|
||||
Opacity = 0.6,
|
||||
Color = Colors.Black
|
||||
},
|
||||
Child = new Grid()
|
||||
};
|
||||
|
||||
// Inner container for the magnified image
|
||||
var innerBorder = new Border
|
||||
{
|
||||
Width = magnifierSize,
|
||||
Height = magnifierSize,
|
||||
CornerRadius = new CornerRadius(12),
|
||||
ClipToBounds = true,
|
||||
Child = new Grid()
|
||||
};
|
||||
|
||||
((Grid)innerBorder.Child).Children.Add(magnifierImage);
|
||||
|
||||
// Center pixel indicator - a rounded border around the center pixel
|
||||
const double pixelSize = magnifierSize / (double)sampleSize; // size of each pixel in the magnified view
|
||||
var centerIndicator = new Border
|
||||
{
|
||||
Width = pixelSize + 4,
|
||||
Height = pixelSize + 4,
|
||||
BorderBrush = new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 255, 255, 255)),
|
||||
BorderThickness = new Thickness(3),
|
||||
CornerRadius = new CornerRadius(4),
|
||||
IsHitTestVisible = false,
|
||||
HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
|
||||
VerticalAlignment = System.Windows.VerticalAlignment.Center,
|
||||
Effect = new System.Windows.Media.Effects.DropShadowEffect
|
||||
{
|
||||
BlurRadius = 4,
|
||||
ShadowDepth = 0,
|
||||
Opacity = 0.8,
|
||||
Color = Colors.White
|
||||
}
|
||||
};
|
||||
|
||||
// Inner shadow border for depth
|
||||
var centerInnerBorder = new Border
|
||||
{
|
||||
Width = pixelSize,
|
||||
Height = pixelSize,
|
||||
BorderBrush = new SolidColorBrush(System.Windows.Media.Color.FromArgb(200, 0, 0, 0)),
|
||||
BorderThickness = new Thickness(2),
|
||||
CornerRadius = new CornerRadius(2),
|
||||
IsHitTestVisible = false,
|
||||
HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
|
||||
VerticalAlignment = System.Windows.VerticalAlignment.Center
|
||||
};
|
||||
|
||||
((Grid)innerBorder.Child).Children.Add(centerIndicator);
|
||||
((Grid)innerBorder.Child).Children.Add(centerInnerBorder);
|
||||
|
||||
((Grid)outerBorder.Child).Children.Add(innerBorder);
|
||||
root.Children.Add(outerBorder);
|
||||
|
||||
void UpdateMagnifier()
|
||||
{
|
||||
var pt = System.Windows.Forms.Control.MousePosition;
|
||||
int cx = pt.X;
|
||||
int cy = pt.Y;
|
||||
|
||||
double left = cx +20;
|
||||
double top = cy +20;
|
||||
|
||||
if (left + outerBorder.Width > SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth)
|
||||
left = cx - outerBorder.Width -20;
|
||||
if (top + outerBorder.Height > SystemParameters.VirtualScreenTop + SystemParameters.VirtualScreenHeight)
|
||||
top = cy - outerBorder.Height -20;
|
||||
|
||||
Canvas.SetLeft(outerBorder, left - SystemParameters.VirtualScreenLeft);
|
||||
Canvas.SetTop(outerBorder, top - SystemParameters.VirtualScreenTop);
|
||||
|
||||
var sampleRectX = cx - (sampleSize /2);
|
||||
var sampleRectY = cy - (sampleSize /2);
|
||||
|
||||
try
|
||||
{
|
||||
using (var bmp = new SD.Bitmap(sampleSize, sampleSize, SD.Imaging.PixelFormat.Format32bppArgb))
|
||||
{
|
||||
using (var g = SD.Graphics.FromImage(bmp))
|
||||
{
|
||||
g.CopyFromScreen(sampleRectX, sampleRectY,0,0, new SD.Size(sampleSize, sampleSize), SD.CopyPixelOperation.SourceCopy);
|
||||
}
|
||||
|
||||
var hBitmap = bmp.GetHbitmap();
|
||||
try
|
||||
{
|
||||
var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight(sampleSize, sampleSize));
|
||||
magnifierImage.Source = bitmapSource;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeleteObject(hBitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
var timer = new DispatcherTimer(DispatcherPriority.Render) { Interval = TimeSpan.FromMilliseconds(30) };
|
||||
timer.Tick += (s, e) => UpdateMagnifier();
|
||||
|
||||
// Mouse click: pick color
|
||||
overlay.MouseLeftButtonDown += (s, e) =>
|
||||
{
|
||||
if (finished) return;
|
||||
|
||||
var pt = System.Windows.Forms.Control.MousePosition;
|
||||
var hdc = GetDC(IntPtr.Zero);
|
||||
try
|
||||
{
|
||||
var pixel = GetPixel(hdc, pt.X, pt.Y);
|
||||
byte r = (byte)(pixel &0x000000FF);
|
||||
byte g = (byte)((pixel &0x0000FF00) >>8);
|
||||
byte b = (byte)((pixel &0x00FF0000) >>16);
|
||||
var color = System.Windows.Media.Color.FromRgb(r, g, b);
|
||||
Finish(color);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { ReleaseDC(IntPtr.Zero, hdc); } catch { }
|
||||
try { timer.Stop(); } catch { }
|
||||
try { System.Windows.Input.Mouse.Capture(null); } catch { }
|
||||
try { overlay.Close(); } catch (InvalidOperationException) { /* ignore if closing */ }
|
||||
}
|
||||
};
|
||||
|
||||
overlay.KeyDown += (s, e) =>
|
||||
{
|
||||
if (e.Key == System.Windows.Input.Key.Escape)
|
||||
{
|
||||
Finish(null);
|
||||
try { timer.Stop(); } catch { }
|
||||
try { System.Windows.Input.Mouse.Capture(null); } catch { }
|
||||
try { overlay.Close(); } catch (InvalidOperationException) { }
|
||||
}
|
||||
};
|
||||
|
||||
overlay.Closed += (s, e) =>
|
||||
{
|
||||
// ensure cleanup
|
||||
Finish(null);
|
||||
try { timer.Stop(); } catch { }
|
||||
try { System.Windows.Input.Mouse.Capture(null); } catch { }
|
||||
};
|
||||
|
||||
// Start timer and show overlay only if application is running
|
||||
timer.Start();
|
||||
try
|
||||
{
|
||||
overlay.Show();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// host window might be closing; abort
|
||||
Finish(null);
|
||||
try { timer.Stop(); } catch { }
|
||||
try { System.Windows.Input.Mouse.Capture(null); } catch { }
|
||||
try { overlay.Close(); } catch { }
|
||||
return;
|
||||
}
|
||||
|
||||
try { overlay.Activate(); } catch { }
|
||||
try { overlay.Focus(); } catch { }
|
||||
try { System.Windows.Input.Mouse.Capture(overlay, System.Windows.Input.CaptureMode.SubTree); } catch { }
|
||||
|
||||
// initial update
|
||||
UpdateMagnifier();
|
||||
});
|
||||
|
||||
return tcs.Task.Result;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
0
MaxSlurper/ThemeResources.xaml
Normal file
0
MaxSlurper/ThemeResources.xaml
Normal file
148
MaxSlurper/ViewModels/MainViewModel.cs
Normal file
148
MaxSlurper/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using MaxSlurper.Models;
|
||||
using MaxSlurper.Services;
|
||||
using MaxSlurper.Audio;
|
||||
using MaxSlurper.Controls;
|
||||
|
||||
namespace MaxSlurper.ViewModels
|
||||
{
|
||||
public class MainViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private readonly IColorPickerService _colorPicker;
|
||||
private readonly IAudioPlayer _audioPlayer;
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
public ObservableCollection<ColorItem> History { get; } = new ObservableCollection<ColorItem>();
|
||||
|
||||
public AppSettings Settings { get; private set; }
|
||||
|
||||
private ColorItem? _selected;
|
||||
public ColorItem? Selected
|
||||
{
|
||||
get => _selected;
|
||||
set { _selected = value; OnPropertyChanged(); UpdateSelected(); ((RelayCommand)CopyHexCommand).RaiseCanExecuteChanged(); }
|
||||
}
|
||||
|
||||
private System.Windows.Media.Brush _selectedBrush = System.Windows.Media.Brushes.White;
|
||||
public System.Windows.Media.Brush SelectedColorBrush { get => _selectedBrush; set { _selectedBrush = value; OnPropertyChanged(); } }
|
||||
|
||||
private string _selectedHex = "#FFFFFF";
|
||||
public string SelectedHex { get => _selectedHex; set { _selectedHex = value; OnPropertyChanged(); } }
|
||||
|
||||
private string _selectedRgb = "255,255,255";
|
||||
public string SelectedRgb { get => _selectedRgb; set { _selectedRgb = value; OnPropertyChanged(); } }
|
||||
|
||||
public ICommand PickColorCommand { get; }
|
||||
public ICommand ClearHistoryCommand { get; }
|
||||
public ICommand CopyHexCommand { get; }
|
||||
public ICommand OpenColorPickerCommand { get; }
|
||||
public ICommand OpenSettingsCommand { get; }
|
||||
|
||||
public MainViewModel(IColorPickerService colorPicker, IAudioPlayer audioPlayer, ISettingsService settingsService)
|
||||
{
|
||||
_colorPicker = colorPicker;
|
||||
_audioPlayer = audioPlayer;
|
||||
_settingsService = settingsService;
|
||||
|
||||
Settings = _settingsService.LoadSettings();
|
||||
|
||||
PickColorCommand = new RelayCommand(async _ => await PickColor());
|
||||
ClearHistoryCommand = new RelayCommand(_ => ClearHistory());
|
||||
CopyHexCommand = new RelayCommand(CopyHex, _ => Selected != null || _ is ColorItem);
|
||||
OpenColorPickerCommand = new RelayCommand(_ => OpenColorPicker());
|
||||
OpenSettingsCommand = new RelayCommand(_ => OpenSettings());
|
||||
}
|
||||
|
||||
public System.Action<AppSettings>? OnSettingsChanged { get; set; }
|
||||
|
||||
private async System.Threading.Tasks.Task PickColor()
|
||||
{
|
||||
var color = await _colorPicker.PickColorAsync();
|
||||
if (color == null) return;
|
||||
|
||||
// play the provided Slurp.wav which is copied to the application's output folder
|
||||
_audioPlayer.Play("Slurp.wav");
|
||||
|
||||
var item = new ColorItem
|
||||
{
|
||||
R = color.Value.R,
|
||||
G = color.Value.G,
|
||||
B = color.Value.B,
|
||||
};
|
||||
item.UpdateDerived();
|
||||
History.Insert(0, item);
|
||||
|
||||
Selected = item;
|
||||
|
||||
System.Windows.Clipboard.SetText(item.Hex);
|
||||
}
|
||||
|
||||
private void OpenColorPicker()
|
||||
{
|
||||
var currentColor = Selected != null
|
||||
? System.Windows.Media.Color.FromRgb(Selected.R, Selected.G, Selected.B)
|
||||
: Colors.Red;
|
||||
|
||||
var picker = new ColorPickerWindow(currentColor);
|
||||
if (picker.ShowDialog() == true)
|
||||
{
|
||||
var color = picker.SelectedColor;
|
||||
|
||||
var item = new ColorItem
|
||||
{
|
||||
R = color.R,
|
||||
G = color.G,
|
||||
B = color.B,
|
||||
};
|
||||
item.UpdateDerived();
|
||||
History.Insert(0, item);
|
||||
|
||||
Selected = item;
|
||||
|
||||
System.Windows.Clipboard.SetText(item.Hex);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenSettings()
|
||||
{
|
||||
var settingsWindow = new SettingsWindow(Settings, _settingsService, (settings) =>
|
||||
{
|
||||
OnSettingsChanged?.Invoke(settings);
|
||||
});
|
||||
settingsWindow.ShowDialog();
|
||||
}
|
||||
|
||||
private void ClearHistory()
|
||||
{
|
||||
History.Clear();
|
||||
}
|
||||
|
||||
private void CopyHex(object? parameter)
|
||||
{
|
||||
var item = parameter as ColorItem ?? Selected;
|
||||
if (item == null) return;
|
||||
|
||||
// Update selected to show which color was clicked
|
||||
Selected = item;
|
||||
|
||||
System.Windows.Clipboard.SetText(item.Hex);
|
||||
|
||||
// Show a notification (optional - can add UI feedback)
|
||||
}
|
||||
|
||||
private void UpdateSelected()
|
||||
{
|
||||
if (Selected == null) return;
|
||||
SelectedColorBrush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(Selected.R, Selected.G, Selected.B));
|
||||
SelectedHex = Selected.Hex;
|
||||
SelectedRgb = Selected.Rgb;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
private void OnPropertyChanged([CallerMemberName] string? name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
}
|
||||
BIN
MaxSlurper/logo.ico
Normal file
BIN
MaxSlurper/logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
BIN
MaxSlurper/logo.png
Normal file
BIN
MaxSlurper/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 404 KiB |
130
build.ps1
Normal file
130
build.ps1
Normal file
@@ -0,0 +1,130 @@
|
||||
# MaxSlurper Build and Package Script
|
||||
# This script builds the MaxSlurper application as a standalone executable
|
||||
|
||||
param(
|
||||
[string]$Version = "1.0.0",
|
||||
[switch]$SkipBuild = $false,
|
||||
[string]$Configuration = "Release"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$StartTime = Get-Date
|
||||
|
||||
# Color output functions
|
||||
function Write-Info { Write-Host $args -ForegroundColor Cyan }
|
||||
function Write-Success { Write-Host $args -ForegroundColor Green }
|
||||
function Write-Error { Write-Host $args -ForegroundColor Red }
|
||||
function Write-Warning { Write-Host $args -ForegroundColor Yellow }
|
||||
|
||||
# Script variables
|
||||
$ScriptRoot = $PSScriptRoot
|
||||
$ProjectName = "MaxSlurper"
|
||||
$ProjectDir = Join-Path $ScriptRoot $ProjectName
|
||||
$ProjectFile = Join-Path $ProjectDir "$ProjectName.csproj"
|
||||
$PublishDir = Join-Path $ScriptRoot "publish"
|
||||
$ReleaseDir = Join-Path $ScriptRoot "release"
|
||||
|
||||
Write-Info "======================================"
|
||||
Write-Info " MaxSlurper Build Script"
|
||||
Write-Info "======================================"
|
||||
Write-Info "Version: $Version"
|
||||
Write-Info "Configuration: $Configuration"
|
||||
Write-Info ""
|
||||
|
||||
# Check if project file exists
|
||||
if (-not (Test-Path $ProjectFile)) {
|
||||
Write-Error "Project file not found: $ProjectFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Clean previous builds
|
||||
Write-Info "Cleaning previous builds..."
|
||||
if (Test-Path $PublishDir) { Remove-Item $PublishDir -Recurse -Force }
|
||||
if (Test-Path $ReleaseDir) { Remove-Item $ReleaseDir -Recurse -Force }
|
||||
|
||||
New-Item -ItemType Directory -Path $PublishDir -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $ReleaseDir -Force | Out-Null
|
||||
|
||||
Write-Success "? Cleanup complete"
|
||||
|
||||
# Build the application
|
||||
if (-not $SkipBuild) {
|
||||
Write-Info ""
|
||||
Write-Info "Building $ProjectName..."
|
||||
|
||||
# Restore dependencies
|
||||
Write-Info "Restoring NuGet packages..."
|
||||
dotnet restore $ProjectFile
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Failed to restore packages"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Publish the application
|
||||
Write-Info "Publishing application..."
|
||||
dotnet publish $ProjectFile `
|
||||
--configuration $Configuration `
|
||||
--output $PublishDir `
|
||||
--runtime win-x64 `
|
||||
--self-contained true `
|
||||
-p:PublishSingleFile=true `
|
||||
-p:IncludeNativeLibrariesForSelfExtract=true `
|
||||
-p:IncludeAllContentForSelfExtract=true `
|
||||
-p:EnableCompressionInSingleFile=true `
|
||||
-p:DebugType=embedded `
|
||||
-p:Version=$Version
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Build failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Success "? Build complete"
|
||||
|
||||
# Copy the executable to release folder
|
||||
$ExePath = Join-Path $PublishDir "$ProjectName.exe"
|
||||
if (Test-Path $ExePath) {
|
||||
Copy-Item $ExePath -Destination (Join-Path $ReleaseDir "$ProjectName-$Version.exe")
|
||||
Copy-Item $ExePath -Destination (Join-Path $ReleaseDir "$ProjectName.exe")
|
||||
Write-Success "? Executable copied to release folder"
|
||||
}
|
||||
|
||||
# Check if Slurp.wav was copied
|
||||
$SlurpWav = Join-Path $PublishDir "Slurp.wav"
|
||||
if (Test-Path $SlurpWav) {
|
||||
Write-Success "? Slurp.wav included"
|
||||
} else {
|
||||
Write-Warning "? Slurp.wav not found - sound may not work"
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Skipping build (using existing files)"
|
||||
}
|
||||
|
||||
# Summary
|
||||
$BuildTime = (Get-Date) - $StartTime
|
||||
Write-Info ""
|
||||
Write-Info "======================================"
|
||||
Write-Success " Build Complete!"
|
||||
Write-Info "======================================"
|
||||
Write-Info ""
|
||||
Write-Info "Release files location: $ReleaseDir"
|
||||
|
||||
if (Test-Path (Join-Path $ReleaseDir "$ProjectName.exe")) {
|
||||
Write-Success "? Executable: MaxSlurper.exe"
|
||||
}
|
||||
|
||||
if (Test-Path (Join-Path $ReleaseDir "$ProjectName-$Version.exe")) {
|
||||
Write-Success "? Versioned: MaxSlurper-$Version.exe"
|
||||
}
|
||||
|
||||
Write-Info ""
|
||||
Write-Info "Build time: $($BuildTime.ToString('mm\:ss'))"
|
||||
Write-Info ""
|
||||
|
||||
# Open release folder
|
||||
$OpenFolder = Read-Host "Open release folder? (Y/N)"
|
||||
if ($OpenFolder -eq "Y" -or $OpenFolder -eq "y") {
|
||||
Start-Process explorer.exe $ReleaseDir
|
||||
}
|
||||
|
||||
Write-Success "Allet jut jemacht! ??"
|
||||
Reference in New Issue
Block a user