Initial first version.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
<Window x:Class="EbbesMemeClipboard.Views.PickerWindow"
|
||||
x:Name="RootWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="clr-namespace:EbbesMemeClipboard.ViewModels"
|
||||
Title="Meme Clipboard"
|
||||
Width="420" Height="520"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
ShowInTaskbar="False"
|
||||
Topmost="True"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="Manual"
|
||||
AllowDrop="True"
|
||||
Deactivated="PickerWindow_OnDeactivated"
|
||||
KeyDown="PickerWindow_OnKeyDown"
|
||||
Drop="PickerWindow_OnDrop"
|
||||
DragOver="PickerWindow_OnDragOver">
|
||||
<Window.Resources>
|
||||
<Style x:Key="FlatButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="#2B2D31"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" CornerRadius="8" SnapsToDevicePixels="True">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="{TemplateBinding Padding}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#3A3D42"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#454952"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Border Background="#F0202225" CornerRadius="10" BorderBrush="#3A3B3E" BorderThickness="1">
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="#2B2D31" CornerRadius="8">
|
||||
<TextBox x:Name="SearchBox"
|
||||
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
|
||||
Background="Transparent" Foreground="White" BorderThickness="0"
|
||||
Padding="10,7" FontSize="14"
|
||||
CaretBrush="White"/>
|
||||
</Border>
|
||||
<Button Grid.Column="1" Content="+" Width="36" Height="36" Margin="8,0,0,0"
|
||||
FontSize="18" Style="{StaticResource FlatButtonStyle}"
|
||||
Command="{Binding ImportFilesCommand}"
|
||||
ToolTip="Add memes"/>
|
||||
</Grid>
|
||||
|
||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||||
<Grid>
|
||||
<TextBlock Text="No memes yet. Click + or drag files in here."
|
||||
Foreground="#7B7D85" FontSize="13"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextWrapping="Wrap" TextAlignment="Center" Width="260">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Items.Count}" Value="0">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Items}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type vm:MemeTileViewModel}">
|
||||
<Button Width="88" Height="88" Margin="4" Padding="0"
|
||||
Style="{StaticResource FlatButtonStyle}"
|
||||
Command="{Binding DataContext.SelectItemCommand, ElementName=RootWindow}"
|
||||
CommandParameter="{Binding}"
|
||||
ToolTip="{Binding DisplayName}">
|
||||
<Image Source="{Binding Thumbnail}" Stretch="Uniform" Width="80" Height="80"/>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<TextBlock Grid.Row="2" Text="{Binding StatusMessage}" Foreground="#9A9CA3" FontSize="11" Margin="2,8,0,0"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using EbbesMemeClipboard.ViewModels;
|
||||
|
||||
namespace EbbesMemeClipboard.Views;
|
||||
|
||||
public partial class PickerWindow : Window
|
||||
{
|
||||
private readonly PickerViewModel _viewModel;
|
||||
|
||||
public PickerWindow(PickerViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
_viewModel = viewModel;
|
||||
DataContext = _viewModel;
|
||||
_viewModel.RequestClose += (_, _) => Hide();
|
||||
}
|
||||
|
||||
public void ShowNearCursor()
|
||||
{
|
||||
PositionNearCursor();
|
||||
_viewModel.OnShown();
|
||||
Show();
|
||||
Activate();
|
||||
SearchBox.Focus();
|
||||
}
|
||||
|
||||
public void ToggleVisibility()
|
||||
{
|
||||
if (IsVisible)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowNearCursor();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Positions the popup near the mouse cursor, clamped to the current monitor's working
|
||||
/// area. The real Windows Emoji Panel can follow the text caret because it's a privileged
|
||||
/// shell component; a third-party app can't replicate that, so cursor position is the
|
||||
/// practical stand-in. Uses a single DPI scale factor for the conversion from WinForms'
|
||||
/// physical pixels to WPF's device-independent units, which is exact for same-DPI
|
||||
/// multi-monitor setups (the common case) and only approximate when the cursor is on a
|
||||
/// secondary monitor running a different DPI scale than the one this window last rendered on.
|
||||
/// </summary>
|
||||
private void PositionNearCursor()
|
||||
{
|
||||
var cursor = System.Windows.Forms.Cursor.Position;
|
||||
var workingArea = System.Windows.Forms.Screen.FromPoint(cursor).WorkingArea;
|
||||
|
||||
double scale = VisualTreeHelper.GetDpi(this).DpiScaleX;
|
||||
|
||||
double left = cursor.X / scale;
|
||||
double top = cursor.Y / scale;
|
||||
double maxLeft = (workingArea.Right / scale) - Width;
|
||||
double maxTop = (workingArea.Bottom / scale) - Height;
|
||||
double minLeft = workingArea.Left / scale;
|
||||
double minTop = workingArea.Top / scale;
|
||||
|
||||
Left = Math.Clamp(left, minLeft, Math.Max(minLeft, maxLeft));
|
||||
Top = Math.Clamp(top, minTop, Math.Max(minTop, maxTop));
|
||||
}
|
||||
|
||||
private void PickerWindow_OnDeactivated(object? sender, EventArgs e) => Hide();
|
||||
|
||||
private void PickerWindow_OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape) Hide();
|
||||
}
|
||||
|
||||
private void PickerWindow_OnDragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private async void PickerWindow_OnDrop(object sender, DragEventArgs e)
|
||||
{
|
||||
if (e.Data.GetData(DataFormats.FileDrop) is string[] paths)
|
||||
{
|
||||
await _viewModel.ImportPathsAsync(paths);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
// This window is reused for the app's whole lifetime - closing it (e.g. Alt+F4) should
|
||||
// just hide it. Real shutdown only happens via the tray icon's Exit command.
|
||||
e.Cancel = true;
|
||||
Hide();
|
||||
base.OnClosing(e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user