Somil55/merge custom ui into launcher (#2271)

* Remove Autosuggest box (#2192)

* Update Settings.Designer.cs

* Revert "Update Settings.Designer.cs"

This reverts commit a1bc0dda56.

* Updated LauncherControl XAML to add textbox and listview

* List View displayed

* Hooking up execution on the selected index, removing two way binding on selection, and experimenting with popup that doesn't work

* Updated MainViewModel to Remove context menu and history

* Added Resultist XAML Island project

* Updated SelectedItem and SelectedList Binding.
Issues :  List box doesn't open when query is written for first time but opens in subsequent queries.

* 1. Mouse Click working
2. List View is can't be focused
3. Fixed width of Launcher

* Removed two way QueryText box binding

* Removed SelectedItem two way binding and replaced with a callback

* [Cleaning] Remove redundant UWP project

* [Cleaning] Updated files to keep only atomic changes against dev/powerLauncher

* Thmbnail fixed for NEW UI

* Removed PreviewMouseDown function required by older WOX code

Co-authored-by: ryanbodrug-microsoft <56318517+ryanbodrug-microsoft@users.noreply.github.com>

* Added the auto-complete feature

* Removing ContextMenuPluginInfo, and ContextMenuTopMost as these commands are not used int the new design.

* Fixed merge conflicts

* Set only when index is 0

* One way binding

* Removed unnecessary binding

* Deleting unused (commented out code) that was legacy from wox project.

* Binding Buttons to appropriate context menu commands.
1. Buttons are dynamically loaded in a listview based on the actions supported be each plugin.

This change also deletes unused commands.

Note:  Most button events don't seem to be getting routed to the Selected Item.  Currently using 'PointerEntered' to validate the behavior.  The actions should be trigged by the button command property in the future.

* manually handling tab in mainwindow

* Loading context buttons on Selecting a suggestion list item

* Allowing hover event to load content menu items and display them as well.

* Adding context buttons to Indexer plugin.  This allows for the following:
1. [Files] Open Containing folder
2. [Folders/Files] Copy Path

* Remove White background of list (#2218)

* Remove white background of list

* Removed comments

* Changed to ContainerContentChanging event

* add const variables instead of numbers

* Added comment before the updatelistSize function

* Search box UI (#2224)

* Added backdrop and rounded corner

* Fix for two alt+space press to bring searchbox issue

* Fixed merge conflict

* Clean Mainwindow.xaml code

* Fix for textbox focus on first visible

* Allowing users to tab between the context buttons for the selected resut.  Also allowing users to press 'enter' to action on the selected items.

* Renaming SelectedIndex to ContextMenuSelectedIndex

* Enabling key accelerators on context buttons.
1. Add new object ContextMenuResult instead instead of reusing Result for both query results and context menu results.
2. Binding KeyboardAccelerator keys to contextmenuitemviewmodel
3. Enabling and disabling contextmenu items when selecting or deselecting each row.  Because we are manually maintaining selectionwe can't use ScopeOwners as the textbox is really the only item ever in focus.

* Launching explorer instead of the UWP application when selecting 'open file location'.

* Added fix for border showing up when result count is zero

* Updated fix for border on no result

* Adding visibility  after clearing result in MainViewmodel

* Launcher Light/Dark mode (#2235)

* Fixed issue with list view background not updating with Windows theme change

* Added theme change for WPF

* updated ShadowDepth for dropshadow

* Updated border thicknes of searchbox and listview

* Diff issue with ResultList.xaml

* Removed change in result delay

* Added code to pull colors from UWP

* Updated border resource to use system based SystemControlHighlightAccentBrush

* Updated corner radius in dark mode

* Updated Launcher description text

Co-authored-by: ryanbodrug-microsoft <56318517+ryanbodrug-microsoft@users.noreply.github.com>
Co-authored-by: Alekhya Reddy <reddykalekhya@gmail.com>
This commit is contained in:
Divyansh Srivastava
2020-04-20 19:53:20 -07:00
committed by GitHub
parent afd22768fc
commit 7da8689bf2
30 changed files with 1285 additions and 638 deletions

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows;
using Wox.Infrastructure.Logger;
using Wox.Plugin;
using Microsoft.Plugin.Indexer.SearchHelper;
namespace Microsoft.Plugin.Indexer
{
internal class ContextMenuLoader : IContextMenu
{
private readonly PluginInitContext _context;
public enum ResultType
{
Folder,
File
}
public ContextMenuLoader(PluginInitContext context)
{
_context = context;
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
var contextMenus = new List<ContextMenuResult>();
if (selectedResult.ContextData is SearchResult record)
{
ResultType type = Path.HasExtension(record.Path) ? ResultType.File : ResultType.Folder;
if (type == ResultType.File)
{
contextMenus.Add(CreateOpenContainingFolderResult(record));
}
var fileOrFolder = (type == ResultType.File) ? "file" : "folder";
contextMenus.Add(new ContextMenuResult
{
Title = "Copy path",
Glyph = "\xE8C8",
FontFamily = "Segoe MDL2 Assets",
SubTitle = $"Copy the current {fileOrFolder} path to clipboard",
AcceleratorKey = "C",
AcceleratorModifiers = "Control",
Action = (context) =>
{
try
{
Clipboard.SetText(record.Path);
return true;
}
catch (Exception e)
{
var message = "Fail to set text in clipboard";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
}
});
}
return contextMenus;
}
private ContextMenuResult CreateOpenContainingFolderResult(SearchResult record)
{
return new ContextMenuResult
{
Title = "Open containing folder",
Glyph = "\xE838",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = "E",
AcceleratorModifiers = "Control,Shift",
Action = _ =>
{
try
{
Process.Start("explorer.exe", $" /select,\"{record.Path}\"");
}
catch(Exception e)
{
var message = $"Fail to open file at {record.Path}";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
return true;
},
};
}
public void LogException(string message, Exception e)
{
Log.Exception($"|Wox.Plugin.Folder.ContextMenu|{message}", e);
}
}
}

View File

@@ -13,7 +13,7 @@ using Microsoft.Search.Interop;
namespace Microsoft.Plugin.Indexer
{
class Main : IPlugin, ISavable, IPluginI18n
class Main : IPlugin, ISavable, IPluginI18n, IContextMenu
{
// This variable contains metadata about the Plugin
@@ -26,7 +26,9 @@ namespace Microsoft.Plugin.Indexer
private PluginJsonStorage<Settings> _storage;
// To access Windows Search functionalities
private readonly WindowsSearchAPI _api = new WindowsSearchAPI();
private readonly WindowsSearchAPI _api = new WindowsSearchAPI();
private IContextMenu _contextMenuLoader;
// To save the configurations of plugins
public void Save()
@@ -109,6 +111,7 @@ namespace Microsoft.Plugin.Indexer
{
// initialize the context of the plugin
_context = context;
_contextMenuLoader = new ContextMenuLoader(context);
_storage = new PluginJsonStorage<Settings>();
_settings = _storage.Load();
}
@@ -125,8 +128,11 @@ namespace Microsoft.Plugin.Indexer
public string GetTranslatedPluginDescription()
{
return "Returns files and folders";
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
return _contextMenuLoader.LoadContextMenus(selectedResult);
}
}
}

View File

@@ -19,23 +19,26 @@ namespace Wox.Plugin.Folder
_context = context;
}
public List<Result> LoadContextMenus(Result selectedResult)
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
var contextMenus = new List<Result>();
var contextMenus = new List<ContextMenuResult>();
if (selectedResult.ContextData is SearchResult record)
{
if (record.Type == ResultType.File)
{
contextMenus.Add(CreateOpenWithEditorResult(record));
contextMenus.Add(CreateOpenContainingFolderResult(record));
}
var icoPath = (record.Type == ResultType.File) ? Main.FileImagePath : Main.FolderImagePath;
var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder";
contextMenus.Add(new Result
contextMenus.Add(new ContextMenuResult
{
Title = "Copy path",
SubTitle = $"Copy the current {fileOrFolder} path to clipboard",
Glyph = "\xE8C8",
FontFamily = "Segoe MDL2 Assets",
SubTitle = $"Copy the current {fileOrFolder} path to clipboard",
AcceleratorKey = "C",
AcceleratorModifiers = "Control",
Action = (context) =>
{
try
@@ -50,91 +53,22 @@ namespace Wox.Plugin.Folder
_context.API.ShowMsg(message);
return false;
}
},
IcoPath = Main.CopyImagePath
}
});
contextMenus.Add(new Result
{
Title = $"Copy {fileOrFolder}",
SubTitle = $"Copy the {fileOrFolder} to clipboard",
Action = (context) =>
{
try
{
Clipboard.SetFileDropList(new System.Collections.Specialized.StringCollection { record.FullPath });
return true;
}
catch (Exception e)
{
var message = $"Fail to set {fileOrFolder} in clipboard";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
},
IcoPath = icoPath
});
if (record.Type == ResultType.File || record.Type == ResultType.Folder)
contextMenus.Add(new Result
{
Title = $"Delete {fileOrFolder}",
SubTitle = $"Delete the selected {fileOrFolder}",
Action = (context) =>
{
try
{
if (record.Type == ResultType.File)
File.Delete(record.FullPath);
else
Directory.Delete(record.FullPath);
}
catch(Exception e)
{
var message = $"Fail to delete {fileOrFolder} at {record.FullPath}";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
return true;
},
IcoPath = Main.DeleteFileFolderImagePath
});
if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath))
contextMenus.Add(new Result
{
Title = "Run as different user",
Action = (context) =>
{
try
{
Task.Run(()=> ShellCommand.RunAsDifferentUser(record.FullPath.SetProcessStartInfo()));
}
catch (FileNotFoundException e)
{
var name = "Plugin: Folder";
var message = $"File not found: {e.Message}";
_context.API.ShowMsg(name, message);
}
return true;
},
IcoPath = "Images/user.png"
});
}
return contextMenus;
}
private Result CreateOpenContainingFolderResult(SearchResult record)
private ContextMenuResult CreateOpenContainingFolderResult(SearchResult record)
{
return new Result
return new ContextMenuResult
{
Title = "Open containing folder",
Glyph = "\xE838",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = "E",
AcceleratorModifiers = "Control,Shift",
Action = _ =>
{
try
@@ -150,8 +84,7 @@ namespace Wox.Plugin.Folder
}
return true;
},
IcoPath = Main.FolderImagePath
}
};
}

View File

@@ -302,7 +302,7 @@ namespace Wox.Plugin.Folder
return _context.API.GetTranslation("wox_plugin_folder_plugin_description");
}
public List<Result> LoadContextMenus(Result selectedResult)
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
return _contextMenuLoader.LoadContextMenus(selectedResult);
}

View File

@@ -141,30 +141,15 @@ namespace Wox.Plugin.Program
return _context.API.GetTranslation("wox_plugin_program_plugin_description");
}
public List<Result> LoadContextMenus(Result selectedResult)
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
var menuOptions = new List<Result>();
var menuOptions = new List<ContextMenuResult>();
var program = selectedResult.ContextData as IProgram;
if (program != null)
{
menuOptions = program.ContextMenus(_context.API);
}
menuOptions.Add(
new Result
{
Title = _context.API.GetTranslation("wox_plugin_program_disable_program"),
Action = c =>
{
DisableProgram(program);
_context.API.ShowMsg(_context.API.GetTranslation("wox_plugin_program_disable_dlgtitle_success"),
_context.API.GetTranslation("wox_plugin_program_disable_dlgtitle_success_message"));
return false;
},
IcoPath = "Images/disable.png"
}
);
return menuOptions;
}

View File

@@ -4,7 +4,7 @@ namespace Wox.Plugin.Program.Programs
{
public interface IProgram
{
List<Result> ContextMenus(IPublicAPI api);
List<ContextMenuResult> ContextMenus(IPublicAPI api);
Result Result(string query, IPublicAPI api);
string UniqueIdentifier { get; set; }
string Name { get; }

View File

@@ -18,7 +18,8 @@ using IStream = AppxPackaing.IStream;
using Rect = System.Windows.Rect;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Media;
using System.Windows.Controls;
namespace Wox.Plugin.Program.Programs
{
[Serializable]
@@ -312,22 +313,23 @@ namespace Wox.Plugin.Program.Programs
return result;
}
public List<Result> ContextMenus(IPublicAPI api)
public List<ContextMenuResult> ContextMenus(IPublicAPI api)
{
var contextMenus = new List<Result>
var contextMenus = new List<ContextMenuResult>
{
new Result
new ContextMenuResult
{
Title = api.GetTranslation("wox_plugin_program_open_containing_folder"),
Glyph = "\xE838",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = "E",
AcceleratorModifiers = "Control,Shift",
Action = _ =>
{
Main.StartProcess(Process.Start, new ProcessStartInfo(Package.Location));
Main.StartProcess(Process.Start, new ProcessStartInfo("explorer", Package.Location));
return true;
},
IcoPath = "Images/folder.png"
}
}
};
return contextMenus;

View File

@@ -94,31 +94,17 @@ namespace Wox.Plugin.Program.Programs
}
public List<Result> ContextMenus(IPublicAPI api)
public List<ContextMenuResult> ContextMenus(IPublicAPI api)
{
var contextMenus = new List<Result>
var contextMenus = new List<ContextMenuResult>
{
new Result
{
Title = api.GetTranslation("wox_plugin_program_run_as_different_user"),
Action = _ =>
{
var info = new ProcessStartInfo
{
FileName = FullPath,
WorkingDirectory = ParentDirectory,
UseShellExecute = true
};
Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info));
return true;
},
IcoPath = "Images/user.png"
},
new Result
new ContextMenuResult
{
Title = api.GetTranslation("wox_plugin_program_run_as_administrator"),
Glyph = "\xE7EF",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = "Enter",
AcceleratorModifiers = "Control,Shift",
Action = _ =>
{
var info = new ProcessStartInfo
@@ -132,12 +118,15 @@ namespace Wox.Plugin.Program.Programs
Task.Run(() => Main.StartProcess(Process.Start, info));
return true;
},
IcoPath = "Images/cmd.png"
}
},
new Result
new ContextMenuResult
{
Title = api.GetTranslation("wox_plugin_program_open_containing_folder"),
Glyph = "\xE838",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = "E",
AcceleratorModifiers = "Control,Shift",
Action = _ =>
{
@@ -145,8 +134,7 @@ namespace Wox.Plugin.Program.Programs
Main.StartProcess(Process.Start, new ProcessStartInfo("explorer", ParentDirectory));
return true;
},
IcoPath = "Images/folder.png"
}
}
};
return contextMenus;

View File

@@ -320,29 +320,22 @@ namespace Wox.Plugin.Shell
return _context.API.GetTranslation("wox_plugin_cmd_plugin_description");
}
public List<Result> LoadContextMenus(Result selectedResult)
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
var resultlist = new List<Result>
var resultlist = new List<ContextMenuResult>
{
new Result
{
Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_different_user"),
Action = c =>
{
Task.Run(() =>Execute(ShellCommand.RunAsDifferentUser, PrepareProcessStartInfo(selectedResult.Title)));
return true;
},
IcoPath = "Images/user.png"
},
new Result
new ContextMenuResult
{
Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_administrator"),
Glyph = "\xE7EF",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = "Enter",
AcceleratorModifiers = "Control,Shift",
Action = c =>
{
Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true));
return true;
},
IcoPath = Image
}
}
};

View File

@@ -13,6 +13,7 @@
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="PrimaryTextColor" Color="Black"/>
<SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="White"/>
<AcrylicBrush x:Key="BackdropAcrylicBrush" BackgroundSource="Backdrop" TintColor="White" TintOpacity="0.9" FallbackColor="White"/>
<AcrylicBrush x:Key="KeyboardShortcutAcrylicBrush" BackgroundSource="Backdrop" TintColor="White" TintOpacity="0.9" FallbackColor="White"/>
<SolidColorBrush x:Key="ItemBackgroundColor" Color="#FFFFFFFF"/>
@@ -22,6 +23,7 @@
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="PrimaryTextColor" Color="White"/>
<SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="#484848"/>
<AcrylicBrush x:Key="BackdropAcrylicBrush" BackgroundSource="Backdrop" TintColor="#88484848" TintOpacity="0.9" FallbackColor="#FF484848"/>
<AcrylicBrush x:Key="KeyboardShortcutAcrylicBrush" BackgroundSource="Backdrop" TintColor="Black" TintOpacity="0.9" FallbackColor="Black"/>
<SolidColorBrush x:Key="ItemBackgroundColor" Color="#FFFFFFFF"/>

View File

@@ -1,74 +1,19 @@
<UserControl
<UserControl
x:Class="PowerLauncher.UI.LauncherControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:ToolkitBehaviors="using:Microsoft.Toolkit.Uwp.UI.Animations.Behaviors"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:ToolkitBehaviors="using:Microsoft.Toolkit.Uwp.UI.Animations.Behaviors"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
ActualThemeChanged="UserControl_ActualThemeChanged"
Loaded="UserControl_Loaded"
d:DesignHeight="300"
d:DesignWidth="720">
<UserControl.Resources>
<Style TargetType="AutoSuggestBox" x:Name="CustomStyledAutoSuggestBox">
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="TextBoxStyle" Value="{StaticResource AutoSuggestBoxTextBoxStyle}" />
<Setter Property="UseSystemFocusVisuals" Value="{ThemeResource IsApplicationFocusVisualKindReveal}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="AutoSuggestBox">
<Grid x:Name="LayoutRoot">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Orientation">
<VisualState x:Name="Landscape" />
<VisualState x:Name="Portrait" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBox
x:Name="TextBox"
Style="{StaticResource CustomAutoSuggestBoxTextBoxStyle}"
PlaceholderText="{TemplateBinding PlaceholderText}"
Header="{TemplateBinding Header}"
Height="72"
Width="{TemplateBinding Width}"
Description="{TemplateBinding Description}"
ScrollViewer.BringIntoViewOnFocusChange="False"
Canvas.ZIndex="0"
Margin="0"
VerticalAlignment="Center"
FontSize="24"
DesiredCandidateWindowAlignment="BottomEdge"
UseSystemFocusVisuals="{TemplateBinding UseSystemFocusVisuals}" />
<Popup x:Name="SuggestionsPopup" Margin="0,24,0,0">
<Border x:Name="SuggestionsContainer">
<ListView
x:Name="SuggestionsList"
Background="{ThemeResource AutoSuggestBoxSuggestionsListBackground}"
BorderThickness="{ThemeResource AutoSuggestListBorderThemeThickness}"
BorderBrush="{ThemeResource AutoSuggestBoxSuggestionsListBorderBrush}"
DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
IsItemClickEnabled="True"
ItemTemplate="{TemplateBinding ItemTemplate}"
ItemTemplateSelector="{TemplateBinding ItemTemplateSelector}"
ItemContainerStyle="{TemplateBinding ItemContainerStyle}"
MaxHeight="{ThemeResource AutoSuggestListMaxHeight}"
Margin="{ThemeResource AutoSuggestListMargin}"
Padding="{ThemeResource AutoSuggestListPadding}" />
</Border>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="TextBox" x:Key="CustomAutoSuggestBoxTextBoxStyle">
<Setter Property="MinWidth" Value="{ThemeResource TextControlThemeMinWidth}" />
<Setter Property="MinHeight" Value="{ThemeResource TextControlThemeMinHeight}" />
@@ -409,79 +354,37 @@
</Setter>
</Style>
</UserControl.Resources>
<Grid
x:Name="PowerBar"
Background="{StaticResource BackdropAcrylicBrush}"
Background="{ThemeResource BackdropAcrylicBrush}"
Height="72"
Translation="0,0,16"
VerticalAlignment="Top">
<AutoSuggestBox
x:Name="SearchBox"
x:FieldModifier="public"
<TextBox
x:Name="AutoCompleteTextBox"
x:FieldModifier="public"
Style="{StaticResource CustomAutoSuggestBoxTextBoxStyle}"
Height="72"
IsReadOnly="True"
AllowFocusOnInteraction="False"
ScrollViewer.BringIntoViewOnFocusChange="False"
Canvas.ZIndex="0"
Margin="0"
VerticalAlignment="Center"
FontSize="24"
DesiredCandidateWindowAlignment="BottomEdge" />
<TextBox
x:Name="TextBox"
x:FieldModifier="public"
Style="{StaticResource CustomAutoSuggestBoxTextBoxStyle}"
PlaceholderText="Start typing"
FontSize="24"
Style="{StaticResource CustomStyledAutoSuggestBox}"
ItemsSource="{Binding Results.Results}">
<AutoSuggestBox.QueryIcon>
<SymbolIcon Symbol="Find"/>
</AutoSuggestBox.QueryIcon>
<AutoSuggestBox.ItemTemplate>
<DataTemplate >
<Grid Height="72" Width="690" Background="Transparent" RowSpacing="0">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="PointerEntered">
<Core:CallMethodAction TargetObject="{Binding ElementName=ShowActionButtons}" MethodName="StartAnimation"/>
</Core:EventTriggerBehavior>
<Core:EventTriggerBehavior EventName="PointerExited">
<Core:CallMethodAction TargetObject="{Binding ElementName=HideActionsButtons}" MethodName="StartAnimation"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions >
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Image x:Name="AppIcon" Height="36" Margin="8,0,0,0" Grid.RowSpan="2" HorizontalAlignment="Left" Source="{Binding Image}" />
<TextBlock x:Name="Title" Grid.Column="1" Text="{Binding Result.Title}" FontWeight="SemiBold" FontSize="20" VerticalAlignment="Bottom"/>
<TextBlock x:Name="Path" Grid.Column="1" Text= "{Binding Result.SubTitle}" Grid.Row="1" Opacity="0.6" VerticalAlignment="Top"/>
<StackPanel Orientation="Horizontal" Opacity="0" HorizontalAlignment="Right" Grid.RowSpan="2" Grid.Column="1">
<Interactivity:Interaction.Behaviors>
<ToolkitBehaviors:Fade x:Name="ShowActionButtons" Duration="250" Delay="0" AutomaticallyStart="False" Value="1" />
<ToolkitBehaviors:Fade x:Name="HideActionsButtons" Duration="250" Delay="0" AutomaticallyStart="False" Value="0" />
</Interactivity:Interaction.Behaviors>
<Button Background="Transparent" Height="42" Width="42" BorderThickness="1" Style="{ThemeResource ButtonRevealStyle}">
<ToolTipService.ToolTip>
<TextBlock Text="Run as administrator"/>
</ToolTipService.ToolTip>
<Button.Content>
<FontIcon FontFamily="Segoe MDL2 Assets" FontSize="16" Glyph="&#xE7EF;"/>
</Button.Content>
</Button>
<Button Background="Transparent" Height="42" Width="42" BorderThickness="1" Style="{ThemeResource ButtonRevealStyle}">
<ToolTipService.ToolTip>
<TextBlock Text="Open file location"/>
</ToolTipService.ToolTip>
<Button.Content>
<FontIcon FontFamily="Segoe MDL2 Assets" FontSize="16" Glyph="&#xE838;"/>
</Button.Content>
</Button>
<Button Background="Transparent" Height="42" Width="42" BorderThickness="1" Style="{ThemeResource ButtonRevealStyle}">
<ToolTipService.ToolTip>
<TextBlock Text="Open in console"/>
</ToolTipService.ToolTip>
<Button.Content>
<FontIcon FontFamily="Segoe MDL2 Assets" FontSize="16" Glyph="&#xE756;"/>
</Button.Content>
</Button>
</StackPanel>
</Grid>
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>
Height="72"
ScrollViewer.BringIntoViewOnFocusChange="False"
Canvas.ZIndex="0"
Margin="0"
VerticalAlignment="Center"
FontSize="24"
DesiredCandidateWindowAlignment="BottomEdge" />
</Grid>
</UserControl>

View File

@@ -1,12 +1,50 @@
using Windows.UI.Xaml.Controls;
namespace PowerLauncher.UI
{
public sealed partial class LauncherControl : UserControl
{
public LauncherControl()
{
InitializeComponent();
}
}
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace PowerLauncher.UI
{
public sealed partial class LauncherControl : UserControl, INotifyPropertyChanged
{
private Brush _borderBrush;
public LauncherControl()
{
InitializeComponent();
}
public Brush SolidBorderBrush
{
get { return _borderBrush; }
set { Set(ref _borderBrush, value); }
}
private void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
private void UserControl_ActualThemeChanged(FrameworkElement sender, object args)
{
SolidBorderBrush = Application.Current.Resources["SystemControlHighlightAccentBrush"] as SolidColorBrush;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
SolidBorderBrush = Application.Current.Resources["SystemControlHighlightAccentBrush"] as SolidColorBrush;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@@ -53,6 +53,9 @@
<DependentUpon>LauncherControl.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ResultList.xaml.cs">
<DependentUpon>ResultList.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
@@ -78,6 +81,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="ResultList.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">

View File

@@ -0,0 +1,101 @@
<UserControl
x:Class="PowerLauncher.UI.ResultList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:ToolkitBehaviors="using:Microsoft.Toolkit.Uwp.UI.Animations.Behaviors"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
d:DesignHeight="300"
d:DesignWidth="720">
<Grid
x:Name="PowerBar"
Background="{ThemeResource BackdropAcrylicBrush}"
Translation="0,0,16"
VerticalAlignment="Top">
<ListView
x:Name="SuggestionsList"
x:FieldModifier="public"
MaxHeight="{Binding Results.MaxHeight}"
MinHeight="{Binding Results.MaxHeight}"
AllowFocusOnInteraction="False"
IsItemClickEnabled="True"
Margin="{ThemeResource AutoSuggestListMargin}"
Padding="{ThemeResource AutoSuggestListPadding}"
ItemsSource="{Binding Results.Results, Mode=OneWay}"
SelectionMode="Single"
SelectedIndex="{Binding Results.SelectedIndex, Mode=TwoWay}"
>
<ListView.ItemTemplate>
<DataTemplate >
<Grid Height="72" Width="690" Background="Transparent" RowSpacing="0">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior Binding="{Binding IsSelected}" ComparisonCondition="Equal" Value="true" >
<Core:CallMethodAction TargetObject="{Binding ElementName=ShowActionButtons}" MethodName="StartAnimation"/>
</Core:DataTriggerBehavior>
<Core:DataTriggerBehavior Binding="{Binding IsSelected}" ComparisonCondition="Equal" Value="false">
<Core:CallMethodAction TargetObject="{Binding ElementName=HideActionsButtons}" MethodName="StartAnimation"/>
</Core:DataTriggerBehavior>
<Core:EventTriggerBehavior EventName="PointerEntered">
<Core:CallMethodAction TargetObject="{Binding ElementName=ShowActionButtons}" MethodName="StartAnimation"/>
<Core:InvokeCommandAction Command="{Binding LoadContextMenuCommand}"/>
</Core:EventTriggerBehavior>
<Core:EventTriggerBehavior EventName="PointerExited">
<Core:CallMethodAction TargetObject="{Binding ElementName=HideActionsButtons}" MethodName="StartAnimation"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions >
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Image x:Name="AppIcon" Height="36" Margin="8,0,0,0" Grid.RowSpan="2" HorizontalAlignment="Left" Source="{Binding Image}" />
<TextBlock x:Name="Title" Grid.Column="1" Text="{Binding Result.Title}" FontWeight="SemiBold" FontSize="20" VerticalAlignment="Bottom"/>
<TextBlock x:Name="Path" Grid.Column="1" Text= "{Binding Result.SubTitle}" Grid.Row="1" Opacity="0.6" VerticalAlignment="Top"/>
<ListView Opacity="0"
HorizontalAlignment="Right"
Grid.RowSpan="2"
Grid.Column="1"
ItemsSource="{Binding ContextMenuItems}"
SelectionMode="Single"
SelectedIndex="{Binding ContextMenuSelectedIndex}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<Interactivity:Interaction.Behaviors>
<ToolkitBehaviors:Fade x:Name="ShowActionButtons" Duration="250" Delay="0" AutomaticallyStart="False" Value="1" />
<ToolkitBehaviors:Fade x:Name="HideActionsButtons" Duration="250" Delay="0" AutomaticallyStart="False" Value="0" />
</Interactivity:Interaction.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Command}" Background="Transparent" Height="42" Width="42" BorderThickness="1" Style="{ThemeResource ButtonRevealStyle}">
<ToolTipService.ToolTip>
<TextBlock Text="{Binding Title}"/>
</ToolTipService.ToolTip>
<Button.Content>
<FontIcon FontFamily="{Binding FontFamily}" FontSize="16" Glyph="{Binding Glyph}"/>
</Button.Content>
<Button.KeyboardAccelerators>
<KeyboardAccelerator
Key="{Binding AcceleratorKey}"
Modifiers="{Binding AcceleratorModifiers}"
IsEnabled="{Binding IsEnabled}"
/>
</Button.KeyboardAccelerators>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</UserControl>

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.UI.Xaml.Controls;
namespace PowerLauncher.UI
{
public sealed partial class ResultList : UserControl
{
public ResultList()
{
InitializeComponent();
}
}
}

View File

@@ -88,7 +88,7 @@ namespace PowerLauncher
RegisterExitEvents();
_mainVM.MainWindowVisibility = Visibility.Hidden;
_mainVM.MainWindowVisibility = Visibility.Visible;
Log.Info("|App.OnStartup|End Wox startup ---------------------------------------------------- ");

View File

@@ -5,7 +5,7 @@
xmlns:vm="clr-namespace:Wox.ViewModel;assembly=Wox"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
mc:Ignorable="d"
xmlns:xaml="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"
Title="PowerLaunch"
Topmost="True"
@@ -23,17 +23,31 @@
Drop="OnDrop"
LocationChanged="OnLocationChanged"
Deactivated="OnDeactivated"
PreviewKeyDown="OnKeyDown"
Background="Transparent"
Background="Transparent"
Visibility="{Binding MainWindowVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
d:DataContext="{d:DesignInstance vm:MainViewModel}">
<Border Margin="10" Background="#fff" BorderBrush="#ccc" BorderThickness="1" CornerRadius="4" Padding="4" Width="720" >
<Border.Effect>
<DropShadowEffect BlurRadius="12" Opacity="0.3" ShadowDepth="4" />
</Border.Effect>
<xaml:WindowsXamlHost InitialTypeName="PowerLauncher.UI.LauncherControl" ChildChanged="WindowsXamlHost_ChildChanged" />
</Border>
<Grid Width="720">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border x:Name="SearchBoxBorder" Margin="10" Grid.Row="0" BorderThickness="4" CornerRadius="4">
<Border.Effect>
<DropShadowEffect BlurRadius="12" Opacity="0.3" ShadowDepth="4" />
</Border.Effect>
<xaml:WindowsXamlHost InitialTypeName="PowerLauncher.UI.LauncherControl" ChildChanged="WindowsXamlHostTextBox_ChildChanged" />
</Border>
<Border Grid.Row="1" Name="myElement" Background="Transparent" Height="10"/>
<Border x:Name="ListBoxBorder" Margin="10" Grid.Row="2" BorderThickness="4" CornerRadius="4" Visibility="{Binding Results.Visbility}">
<Border.Effect>
<DropShadowEffect BlurRadius="12" Opacity="0.3" ShadowDepth="4" />
</Border.Effect>
<xaml:WindowsXamlHost PreviewMouseDown="WindowsXamlHost_PreviewMouseDown" InitialTypeName="PowerLauncher.UI.ResultList" ChildChanged="WindowsXamlHostListView_ChildChanged" />
</Border>
</Grid>
<Window.InputBindings>
<KeyBinding Key="Escape" Command="{Binding EscCommand}" />
<KeyBinding Key="Enter" Command="{Binding OpenResultCommand}"></KeyBinding>
</Window.InputBindings>
</Window>

View File

@@ -16,7 +16,12 @@ using KeyEventArgs = System.Windows.Input.KeyEventArgs;
using MessageBox = System.Windows.MessageBox;
using Microsoft.Toolkit.Wpf.UI.XamlHost;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.System;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Core;
using System.Windows.Media;
namespace PowerLauncher
{
@@ -29,6 +34,10 @@ namespace PowerLauncher
private Settings _settings;
private MainViewModel _viewModel;
const int ROW_COUNT = 4;
const int ROW_HEIGHT = 75;
const int MAX_LIST_HEIGHT = 300;
#endregion
public MainWindow(Settings settings, MainViewModel mainVM)
@@ -37,6 +46,7 @@ namespace PowerLauncher
_viewModel = mainVM;
_settings = settings;
InitializeComponent();
}
public MainWindow()
{
@@ -52,35 +62,8 @@ namespace PowerLauncher
{
}
private void OnLoaded(object sender, RoutedEventArgs _)
private void OnLoaded(object sender, System.Windows.RoutedEventArgs _)
{
// todo is there a way to set blur only once?
//ThemeManager.Instance.SetBlurForWindow();
//WindowsInteropHelper.DisableControlBox(this);
//InitProgressbarAnimation();
//InitializePosition();
//// since the default main window visibility is visible
//// so we need set focus during startup
//QueryTextBox.Focus();
//_viewModel.PropertyChanged += (o, e) =>
//{
// if (e.PropertyName == nameof(MainViewModel.MainWindowVisibility))
// {
// if (Visibility == Visibility.Visible)
// {
// Activate();
// QueryTextBox.Focus();
// UpdatePosition();
// _settings.ActivateTimes++;
// if (!_viewModel.LastQuerySelected)
// {
// QueryTextBox.SelectAll();
// _viewModel.LastQuerySelected = true;
// }
// }
// }
//};
InitializePosition();
}
@@ -92,49 +75,11 @@ namespace PowerLauncher
_settings.WindowLeft = Left;
}
//private void InitProgressbarAnimation()
//{
// var da = new DoubleAnimation(ProgressBar.X2, ActualWidth + 100, new Duration(new TimeSpan(0, 0, 0, 0, 1600)));
// var da1 = new DoubleAnimation(ProgressBar.X1, ActualWidth, new Duration(new TimeSpan(0, 0, 0, 0, 1600)));
// Storyboard.SetTargetProperty(da, new PropertyPath("(Line.X2)"));
// Storyboard.SetTargetProperty(da1, new PropertyPath("(Line.X1)"));
// _progressBarStoryboard.Children.Add(da);
// _progressBarStoryboard.Children.Add(da1);
// _progressBarStoryboard.RepeatBehavior = RepeatBehavior.Forever;
// ProgressBar.BeginStoryboard(_progressBarStoryboard);
// _viewModel.ProgressBarVisibility = Visibility.Hidden;
//}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left) DragMove();
}
//private void OnPreviewMouseButtonDown(object sender, MouseButtonEventArgs e)
//{
// if (sender != null && e.OriginalSource != null)
// {
// var r = (ResultListBox)sender;
// var d = (DependencyObject)e.OriginalSource;
// var item = ItemsControl.ContainerFromElement(r, d) as ListBoxItem;
// var result = (ResultViewModel)item?.DataContext;
// if (result != null)
// {
// if (e.ChangedButton == MouseButton.Left)
// {
// _viewModel.OpenResultCommand.Execute(null);
// }
// else if (e.ChangedButton == MouseButton.Right)
// {
// _viewModel.LoadContextMenuCommand.Execute(null);
// }
// }
// }
//}
private void OnDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
@@ -158,10 +103,6 @@ namespace PowerLauncher
e.Handled = true;
}
private void OnContextMenusForSettingsClick(object sender, RoutedEventArgs e)
{
}
private void OnDeactivated(object sender, EventArgs e)
{
if (_settings.HideWhenDeactive)
@@ -202,63 +143,19 @@ namespace PowerLauncher
return left;
}
//private double WindowTop()
//{
// var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
// var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
// var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
// var top = (dip2.Y - QueryTextBox.ActualHeight) / 4 + dip1.Y;
// return top;
//}
/// <summary>
/// Register up and down key
/// todo: any way to put this in xaml ?
/// </summary>
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Down)
{
_viewModel.SelectNextItemCommand.Execute(null);
e.Handled = true;
}
else if (e.Key == Key.Up)
{
_viewModel.SelectPrevItemCommand.Execute(null);
e.Handled = true;
}
else if (e.Key == Key.PageDown)
{
_viewModel.SelectNextPageCommand.Execute(null);
e.Handled = true;
}
else if (e.Key == Key.PageUp)
{
_viewModel.SelectPrevPageCommand.Execute(null);
e.Handled = true;
}
}
//private void OnTextChanged(object sender, TextChangedEventArgs e)
//{
// if (_viewModel.QueryTextCursorMovedToEnd)
// {
// QueryTextBox.CaretIndex = QueryTextBox.Text.Length;
// _viewModel.QueryTextCursorMovedToEnd = false;
// }
//}
private PowerLauncher.UI.LauncherControl _launcher = null;
private void WindowsXamlHost_ChildChanged(object sender, EventArgs ev)
private void WindowsXamlHostTextBox_ChildChanged(object sender, EventArgs ev)
{
if (sender == null) return;
var host = (WindowsXamlHost)sender;
_launcher = (PowerLauncher.UI.LauncherControl)host.Child;
_launcher.DataContext = _viewModel;
_launcher.SearchBox.TextChanged += QueryTextBox_TextChanged;
_launcher.SearchBox.QuerySubmitted += AutoSuggestBox_QuerySubmitted;
_launcher.SearchBox.Focus(Windows.UI.Xaml.FocusState.Programmatic);
_launcher.KeyDown += _launcher_KeyDown;
_launcher.TextBox.TextChanged += QueryTextBox_TextChanged;
_launcher.TextBox.Loaded += TextBox_Loaded;
_launcher.PropertyChanged += UserControl_PropertyChanged;
_viewModel.PropertyChanged += (o, e) =>
{
if (e.PropertyName == nameof(MainViewModel.MainWindowVisibility))
@@ -266,16 +163,141 @@ namespace PowerLauncher
if (Visibility == System.Windows.Visibility.Visible)
{
Activate();
_launcher.SearchBox.Focus(Windows.UI.Xaml.FocusState.Programmatic);
UpdatePosition();
_settings.ActivateTimes++;
if (!_viewModel.LastQuerySelected)
{
_viewModel.LastQuerySelected = true;
}
}
}
}
};
};
}
private void UserControl_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "SolidBorderBrush")
{
if (_launcher != null)
{
Windows.UI.Xaml.Media.SolidColorBrush uwpBrush = _launcher.SolidBorderBrush as Windows.UI.Xaml.Media.SolidColorBrush;
System.Windows.Media.Color borderColor = System.Windows.Media.Color.FromArgb(uwpBrush.Color.A, uwpBrush.Color.R, uwpBrush.Color.G, uwpBrush.Color.B);
this.SearchBoxBorder.BorderBrush = new SolidColorBrush(borderColor);
this.ListBoxBorder.BorderBrush = new SolidColorBrush(borderColor);
}
}
}
private void TextBox_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
TextBox tb = (TextBox)sender;
tb.Focus(FocusState.Programmatic);
_viewModel.MainWindowVisibility = System.Windows.Visibility.Collapsed;
}
private UI.ResultList _resultList = null;
private void WindowsXamlHostListView_ChildChanged(object sender, EventArgs ev)
{
if (sender == null) return;
var host = (WindowsXamlHost)sender;
_resultList = (UI.ResultList)host.Child;
_resultList.DataContext = _viewModel;
_resultList.Tapped += SuggestionsList_Tapped;
_resultList.SuggestionsList.SelectionChanged += SuggestionsList_SelectionChanged;
_resultList.SuggestionsList.ContainerContentChanging += SuggestionList_UpdateListSize;
}
private bool IsKeyDown(VirtualKey key)
{
var keyState = CoreWindow.GetForCurrentThread().GetKeyState(key);
return (keyState & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down;
}
private void _launcher_KeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Tab && IsKeyDown(VirtualKey.Shift))
{
_viewModel.SelectPrevTabItemCommand.Execute(null);
e.Handled = true;
}
else if (e.Key == VirtualKey.Tab)
{
_viewModel.SelectNextTabItemCommand.Execute(null);
e.Handled = true;
}
else if (e.Key == VirtualKey.Down)
{
_viewModel.SelectNextItemCommand.Execute(null);
e.Handled = true;
}
else if (e.Key == VirtualKey.Up)
{
_viewModel.SelectPrevItemCommand.Execute(null);
e.Handled = true;
}
else if (e.Key == VirtualKey.PageDown)
{
_viewModel.SelectNextPageCommand.Execute(null);
e.Handled = true;
}
else if (e.Key == VirtualKey.PageUp)
{
_viewModel.SelectPrevPageCommand.Execute(null);
e.Handled = true;
}
}
private void SuggestionsList_Tapped(object sender, TappedRoutedEventArgs e)
{
var result = ((Windows.UI.Xaml.FrameworkElement)e.OriginalSource).DataContext;
if (result != null)
{
var resultVM = result as ResultViewModel;
//This may be null if the tapped item was one of the context buttons (run as admin etc).
if (resultVM != null)
{
_viewModel.Results.SelectedItem = resultVM;
_viewModel.OpenResultCommand.Execute(null);
}
}
}
/* Note: This function has been added because a white-background was observed when the list resized,
* when the number of elements were lesser than the maximum capacity of the list (ie. 4).
* Binding Height/MaxHeight Properties did not solve this issue.
*/
private void SuggestionList_UpdateListSize(object sender, ContainerContentChangingEventArgs e)
{
int count = _viewModel?.Results?.Results.Count ?? 0;
int maxHeight = count < ROW_COUNT ? count * ROW_HEIGHT : MAX_LIST_HEIGHT;
_resultList.Height = maxHeight;
}
private void SuggestionsList_SelectionChanged(object sender, Windows.UI.Xaml.Controls.SelectionChangedEventArgs e)
{
Windows.UI.Xaml.Controls.ListView listview = (Windows.UI.Xaml.Controls.ListView)sender;
_viewModel.Results.SelectedItem = (ResultViewModel) listview.SelectedItem;
if (e.AddedItems.Count > 0 && e.AddedItems[0] != null)
{
listview.ScrollIntoView(e.AddedItems[0]);
}
// To populate the AutoCompleteTextBox as soon as the selection is changed or set.
// Setting it here instead of when the text is changed as there is a delay in executing the query and populating the result
_launcher.AutoCompleteTextBox.PlaceholderText = ListView_FirstItem(_viewModel.QueryText);
}
private void ResultsList_ItemClick(object sender, ItemClickEventArgs e)
{
ResultViewModel result = e?.ClickedItem as ResultViewModel;
if(result != null)
{
_viewModel.Results.SelectedItem = result;
_viewModel.OpenResultCommand.Execute(null);
}
}
private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
@@ -291,12 +313,75 @@ namespace PowerLauncher
}
}
private void QueryTextBox_TextChanged(Windows.UI.Xaml.Controls.AutoSuggestBox sender, Windows.UI.Xaml.Controls.AutoSuggestBoxTextChangedEventArgs args)
private const int millisecondsToWait = 200;
private static DateTime s_lastTimeOfTyping;
private string ListView_FirstItem(String input)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
string s = input;
if (s.Length > 0)
{
_viewModel.QueryText = sender.Text;
String selectedItem = _viewModel.Results?.SelectedItem?.ToString();
int selectedIndex = _viewModel.Results.SelectedIndex;
if (selectedItem != null && selectedIndex == 0)
{
if (selectedItem.IndexOf(input) == 0)
{
return selectedItem;
}
}
}
return String.Empty;
}
private void QueryTextBox_TextChanged(object sender, Windows.UI.Xaml.Controls.TextChangedEventArgs e)
{
var latestTimeOfTyping = DateTime.Now;
var text = ((Windows.UI.Xaml.Controls.TextBox)sender).Text;
Task.Run(() => DelayedCheck(latestTimeOfTyping, text));
s_lastTimeOfTyping = latestTimeOfTyping;
//To clear the auto-suggest immediately instead of waiting for selection changed
if(text == String.Empty)
{
_launcher.AutoCompleteTextBox.PlaceholderText = String.Empty;
}
}
private async Task DelayedCheck(DateTime latestTimeOfTyping, string text)
{
await Task.Delay(millisecondsToWait);
if (latestTimeOfTyping.Equals(s_lastTimeOfTyping))
{
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
_viewModel.QueryText = text;
}));
}
}
private void WindowsXamlHost_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// if (sender != null && e.OriginalSource != null)
// {
// //var r = (ResultListBox)sender;
// //var d = (DependencyObject)e.OriginalSource;
// //var item = ItemsControl.ContainerFromElement(r, d) as ListBoxItem;
// //var result = (ResultViewModel)item?.DataContext;
// //if (result != null)
// //{
// // if (e.ChangedButton == MouseButton.Left)
// // {
// // _viewModel.OpenResultCommand.Execute(null);
// // }
// // else if (e.ChangedButton == MouseButton.Right)
// // {
// // _viewModel.LoadContextMenuCommand.Execute(null);
// // }
// //}
// }
}
}
}

View File

@@ -61,6 +61,7 @@
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<PackageReference Include="Mages" Version="1.6.0" />
<PackageReference Include="Microsoft.Toolkit.UI.XamlHost" Version="6.0.0" />
<PackageReference Include="Microsoft.Toolkit.Uwp.UI" Version="6.0.0" />
<PackageReference Include="Microsoft.Toolkit.Wpf.UI.XamlHost" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NHotkey.Wpf" Version="2.0.0" />

View File

@@ -7,7 +7,6 @@
xmlns:converter="clr-namespace:Wox.Converters;assembly=Wox"
mc:Ignorable="d" d:DesignWidth="100" d:DesignHeight="100"
d:DataContext="{d:DesignInstance vm:ResultsViewModel}"
MaxHeight="{Binding MaxHeight}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}"
HorizontalContentAlignment="Stretch" ItemsSource="{Binding Results}"

View File

@@ -45,16 +45,20 @@ namespace Wox.Core.Plugin
}
}
public List<Result> LoadContextMenus(Result selectedResult)
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
string output = ExecuteContextMenu(selectedResult);
try
{
return DeserializedResult(output);
//This should not hit. If it does it's because Wox shares the same interface for querying context menu items as well as search results. In this case please file a bug.
//To my knowledge we aren't supporting this JSonRPC commands in Launcher, and am not able to repro this, but I will leave this here for the time being in case I'm proven wrong.
//We should remove this, or identify and test officially supported use cases and Deserialize this properly.
//return DeserializedResult(output);
throw new NotImplementedException();
}
catch (Exception e)
{
Log.Exception($"|JsonRPCPlugin.LoadContextMenus|Exception on result <{selectedResult}>", e);
Log.Exception($"|JsonRPCPlugin.LoadContextMenus| THIS IS A BUG - Exception on result <{selectedResult}>", e);
return null;
}
}

View File

@@ -198,7 +198,7 @@ namespace Wox.Core.Plugin
return AllPlugins.Where(p => p.Plugin is T);
}
public static List<Result> GetContextMenusForPlugin(Result result)
public static List<ContextMenuResult> GetContextMenusForPlugin(Result result)
{
var pluginPair = _contextMenuPlugins.FirstOrDefault(o => o.Metadata.ID == result.PluginID);
if (pluginPair != null)
@@ -209,23 +209,17 @@ namespace Wox.Core.Plugin
try
{
var results = plugin.LoadContextMenus(result);
foreach (var r in results)
{
r.PluginDirectory = metadata.PluginDirectory;
r.PluginID = metadata.ID;
r.OriginQuery = result.OriginQuery;
}
return results;
}
catch (Exception e)
{
Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{metadata.Name}>", e);
return new List<Result>();
return new List<ContextMenuResult>();
}
}
else
{
return new List<Result>();
return new List<ContextMenuResult>();
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.IO;
using Windows.UI.Xaml.Media;
namespace Wox.Plugin
{
public class ContextMenuResult
{
public string Title { get; set; }
public string SubTitle { get; set; }
public string Glyph { get; set; }
public string FontFamily { get; set; }
public string AcceleratorKey { get; set; }
public string AcceleratorModifiers { get; set; }
/// <summary>
/// return true to hide wox after select result
/// </summary>
public Func<ActionContext, bool> Action { get; set; }
public override string ToString()
{
return Title + SubTitle;
}
}
}

View File

@@ -8,7 +8,7 @@ namespace Wox.Plugin
public interface IContextMenu : IFeatures
{
List<Result> LoadContextMenus(Result selectedResult);
List<ContextMenuResult> LoadContextMenus(Result selectedResult);
}
[Obsolete("If a plugin has a action keyword, then it is exclusive. This interface will be remove in v1.3.0")]

View File

@@ -14,6 +14,10 @@ namespace Wox.Plugin
public string Title { get; set; }
public string SubTitle { get; set; }
public string Glyph { get; set; }
public string FontFamily { get; set; }
public string IcoPath
{
get { return _icoPath; }

View File

@@ -0,0 +1,17 @@
using System.Drawing;
using System.Windows.Input;
using Wox.Plugin;
namespace Wox.ViewModel
{
public class ContextMenuItemViewModel : BaseModel
{
public string Title { get; set; }
public string Glyph { get; set; }
public string FontFamily { get; set; }
public ICommand Command { get; set; }
public string AcceleratorKey { get; set; }
public string AcceleratorModifiers { get; set; }
public bool IsEnabled { get; set; }
}
}

View File

@@ -114,6 +114,16 @@ namespace Wox.ViewModel
SelectPrevItemCommand = new RelayCommand(_ =>
{
SelectedResults.SelectPrevResult();
});
SelectNextTabItemCommand = new RelayCommand(_ =>
{
SelectedResults.SelectNextTabItem();
});
SelectPrevTabItemCommand = new RelayCommand(_ =>
{
SelectedResults.SelectPrevTabItem();
});
SelectNextPageCommand = new RelayCommand(_ =>
@@ -142,27 +152,33 @@ namespace Wox.ViewModel
results.SelectedIndex = int.Parse(index.ToString());
}
var result = results.SelectedItem?.Result;
if (result != null) // SelectedItem returns null if selection is empty.
{
bool hideWindow = result.Action != null && result.Action(new ActionContext
{
SpecialKeyState = GlobalHotkey.Instance.CheckModifiers()
});
//If there is a context button selected fire the action for that button before the main command.
bool didExecuteContextButton = results.SelectedItem?.ExecuteSelectedContextButton() ?? false;
if (hideWindow)
if (!didExecuteContextButton)
{
var result = results.SelectedItem?.Result;
if (result != null) // SelectedItem returns null if selection is empty.
{
MainWindowVisibility = Visibility.Collapsed;
}
bool hideWindow = result.Action != null && result.Action(new ActionContext
{
SpecialKeyState = GlobalHotkey.Instance.CheckModifiers()
});
if (SelectedIsFromQueryResults())
{
_userSelectedRecord.Add(result);
_history.Add(result.OriginQuery.RawQuery);
}
else
{
SelectedResults = Results;
if (hideWindow)
{
MainWindowVisibility = Visibility.Collapsed;
}
if (SelectedIsFromQueryResults())
{
_userSelectedRecord.Add(result);
_history.Add(result.OriginQuery.RawQuery);
}
else
{
SelectedResults = Results;
}
}
}
});
@@ -270,7 +286,11 @@ namespace Wox.ViewModel
public ICommand EscCommand { get; set; }
public ICommand SelectNextItemCommand { get; set; }
public ICommand SelectPrevItemCommand { get; set; }
public ICommand SelectPrevItemCommand { get; set; }
public ICommand SelectNextTabItemCommand { get; set; }
public ICommand SelectPrevTabItemCommand { get; set; }
public ICommand SelectNextPageCommand { get; set; }
public ICommand SelectPrevPageCommand { get; set; }
public ICommand SelectFirstResultCommand { get; set; }
@@ -287,46 +307,12 @@ namespace Wox.ViewModel
{
QueryResults();
}
else if (ContextMenuSelected())
{
QueryContextMenu();
}
else if (HistorySelected())
{
QueryHistory();
}
}
private void QueryContextMenu()
{
const string id = "Context Menu ID";
var query = QueryText.ToLower().Trim();
ContextMenu.Clear();
var selected = Results.SelectedItem?.Result;
if (selected != null) // SelectedItem returns null if selection is empty.
{
var results = PluginManager.GetContextMenusForPlugin(selected);
results.Add(ContextMenuTopMost(selected));
results.Add(ContextMenuPluginInfo(selected.PluginID));
if (!string.IsNullOrEmpty(query))
{
var filtered = results.Where
(
r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet()
|| StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet()
).ToList();
ContextMenu.AddResults(filtered, id);
}
else
{
ContextMenu.AddResults(results, id);
}
}
}
private void QueryHistory()
{
const string id = "Query History ID";
@@ -387,9 +373,9 @@ namespace Wox.ViewModel
// handle the exclusiveness of plugin using action keyword
RemoveOldQueryResults(query);
_lastQuery = query;
_lastQuery = query;
var plugins = PluginManager.ValidPluginsForQuery(query);
Task.Run(() =>
{
// so looping will stop once it was cancelled
@@ -463,66 +449,6 @@ namespace Wox.ViewModel
}
private Result ContextMenuTopMost(Result result)
{
Result menu;
if (_topMostRecord.IsTopMost(result))
{
menu = new Result
{
Title = InternationalizationManager.Instance.GetTranslation("cancelTopMostInThisQuery"),
IcoPath = "Images\\down.png",
PluginDirectory = Constant.ProgramDirectory,
Action = _ =>
{
_topMostRecord.Remove(result);
App.API.ShowMsg("Success");
return false;
}
};
}
else
{
menu = new Result
{
Title = InternationalizationManager.Instance.GetTranslation("setAsTopMostInThisQuery"),
IcoPath = "Images\\up.png",
PluginDirectory = Constant.ProgramDirectory,
Action = _ =>
{
_topMostRecord.AddOrUpdate(result);
App.API.ShowMsg("Success");
return false;
}
};
}
return menu;
}
private Result ContextMenuPluginInfo(string id)
{
var metadata = PluginManager.GetPluginForId(id).Metadata;
var translator = InternationalizationManager.Instance;
var author = translator.GetTranslation("author");
var website = translator.GetTranslation("website");
var version = translator.GetTranslation("version");
var plugin = translator.GetTranslation("plugin");
var title = $"{plugin}: {metadata.Name}";
var icon = metadata.IcoPath;
var subtitle = $"{author}: {metadata.Author}, {website}: {metadata.Website} {version}: {metadata.Version}";
var menu = new Result
{
Title = title,
IcoPath = icon,
SubTitle = subtitle,
PluginDirectory = metadata.PluginDirectory,
Action = _ => false
};
return menu;
}
private bool SelectedIsFromQueryResults()
{
var selected = SelectedResults == Results;

View File

@@ -1,69 +1,186 @@
using System;
using Wox.Infrastructure;
using Wox.Infrastructure.Image;
using Wox.Infrastructure.Logger;
using Wox.Plugin;
using Windows.UI.Xaml.Media;
namespace Wox.ViewModel
{
public class ResultViewModel : BaseModel
{
public ResultViewModel(Result result)
{
if (result != null)
{
Result = result;
}
}
public ImageSource Image
{
get
{
var imagePath = Result.IcoPath;
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
{
try
{
return Result.Icon();
}
catch (Exception e)
{
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
imagePath = Constant.ErrorIcon;
}
}
// will get here either when icoPath has value\icon delegate is null\when had exception in delegate
return ImageLoader.Load(imagePath);
}
}
public Result Result { get; }
public override bool Equals(object obj)
{
var r = obj as ResultViewModel;
if (r != null)
{
return Result.Equals(r.Result);
}
else
{
return false;
}
}
public override int GetHashCode()
{
return Result.GetHashCode();
}
public override string ToString()
{
return Result.Title.ToString();
}
}
}
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using System.Windows.Controls.Ribbon;
using System.Windows.Input;
using Wox.Core.Plugin;
using Wox.Infrastructure;
using Wox.Infrastructure.Hotkey;
using Wox.Infrastructure.Image;
using Wox.Infrastructure.Logger;
using Wox.Plugin;
using Windows.UI.Xaml.Media;
namespace Wox.ViewModel
{
public class ResultViewModel : BaseModel
{
public List<ContextMenuItemViewModel> ContextMenuItems { get; set; }
public ICommand LoadContextMenuCommand { get; set; }
public bool IsSelected { get; set; }
public int ContextMenuSelectedIndex { get; set; }
const int NoSelectionIndex = -1;
public ResultViewModel(Result result)
{
if (result != null)
{
Result = result;
}
ContextMenuSelectedIndex = NoSelectionIndex;
LoadContextMenuCommand = new RelayCommand(LoadContextMenu);
}
public void LoadContextMenu(object sender=null)
{
var results = PluginManager.GetContextMenusForPlugin(Result);
var newItems = new List<ContextMenuItemViewModel>();
foreach (var r in results)
{
newItems.Add(new ContextMenuItemViewModel
{
Title = r.Title,
Glyph = r.Glyph,
FontFamily = r.FontFamily,
AcceleratorKey = r.AcceleratorKey,
AcceleratorModifiers = r.AcceleratorModifiers,
Command = new RelayCommand(_ =>
{
bool hideWindow = r.Action != null && r.Action(new ActionContext
{
SpecialKeyState = GlobalHotkey.Instance.CheckModifiers()
});
if (hideWindow)
{
//TODO - Do we hide the window
// MainWindowVisibility = Visibility.Collapsed;
}
})
});
}
ContextMenuItems = newItems;
}
internal void EnableContextMenu()
{
foreach(var i in ContextMenuItems)
{
i.IsEnabled = true;
}
}
internal void DisableContextMenu()
{
foreach (var i in ContextMenuItems)
{
i.IsEnabled = false;
}
}
public ImageSource Image
{
get
{
var imagePath = Result.IcoPath;
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
{
try
{
return Result.Icon();
}
catch (Exception e)
{
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
imagePath = Constant.ErrorIcon;
}
}
// will get here either when icoPath has value\icon delegate is null\when had exception in delegate
return ImageLoader.Load(imagePath);
}
}
//Returns false if we've already reached the last item.
public bool SelectNextContextButton()
{
if(ContextMenuSelectedIndex == (ContextMenuItems.Count -1))
{
ContextMenuSelectedIndex = NoSelectionIndex;
return false;
}
ContextMenuSelectedIndex++;
return true;
}
//Returns false if we've already reached the first item.
public bool SelectPrevContextButton()
{
if (ContextMenuSelectedIndex == NoSelectionIndex)
{
return false;
}
ContextMenuSelectedIndex--;
return true;
}
public void SelectLastContextButton()
{
ContextMenuSelectedIndex = ContextMenuItems.Count - 1;
}
public bool HasSelectedContextButton()
{
var isContextSelected = (ContextMenuSelectedIndex != NoSelectionIndex);
return isContextSelected;
}
/// <summary>
/// Triggers the action on the selected context button
/// </summary>
/// <returns>False if there is nothing selected, oherwise true</returns>
public bool ExecuteSelectedContextButton()
{
if (HasSelectedContextButton())
{
ContextMenuItems[ContextMenuSelectedIndex].Command.Execute(null);
return true;
}
return false;
}
public Result Result { get; }
public override bool Equals(object obj)
{
var r = obj as ResultViewModel;
if (r != null)
{
return Result.Equals(r.Result);
}
else
{
return false;
}
}
public override int GetHashCode()
{
return Result.GetHashCode();
}
public override string ToString()
{
return Result.Title.ToString();
}
}
}

View File

@@ -6,6 +6,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using Wox.Infrastructure.UserSettings;
using Wox.Plugin;
@@ -47,9 +48,33 @@ namespace Wox.ViewModel
public int SelectedIndex { get; set; }
public ResultViewModel SelectedItem { get; set; }
private ResultViewModel _selectedItem;
public ResultViewModel SelectedItem
{
get { return _selectedItem; }
set
{
//value can be null when selecting an item in a virtualized list
if (value != null)
{
if (_selectedItem != null)
{
_selectedItem.IsSelected = false;
_selectedItem.DisableContextMenu();
}
_selectedItem = value;
_selectedItem.LoadContextMenu();
_selectedItem.EnableContextMenu();
_selectedItem.IsSelected = true;
}
}
}
public Thickness Margin { get; set; }
public Visibility Visbility { get; set; } = Visibility.Collapsed;
public Visibility Visbility { get; set; } = Visibility.Hidden;
#endregion
@@ -127,8 +152,26 @@ namespace Wox.ViewModel
public void RemoveResultsFor(PluginMetadata metadata)
{
Results.RemoveAll(r => r.Result.PluginID == metadata.ID);
}
}
public void SelectNextTabItem()
{
if(!SelectedItem.SelectNextContextButton())
{
SelectNextResult();
}
}
public void SelectPrevTabItem()
{
if (!SelectedItem.SelectPrevContextButton())
{
//Tabbing backwards should highlight the last item of the previous row
SelectPrevResult();
SelectedItem.SelectLastContextButton();
}
}
/// <summary>
/// To avoid deadlock, this method should not called from main thread
/// </summary>
@@ -149,6 +192,7 @@ namespace Wox.ViewModel
else
{
Margin = new Thickness { Top = 0 };
Visbility = Visibility.Collapsed;
}
}
}