Implement theme settings pane

This commit is contained in:
Yusyuriv 2024-05-16 05:38:31 +06:00
parent f7b2d9df3b
commit 5c05b69c05
No known key found for this signature in database
GPG key ID: A91C52E6F73148E0
3 changed files with 442 additions and 45 deletions

View file

@ -0,0 +1,371 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.ViewModels;
public class SettingsPaneThemeViewModel : BaseModel
{
private CultureInfo Culture => CultureInfo.DefaultThreadCurrentCulture;
public Settings Settings { get; }
public static string LinkHowToCreateTheme => @"https://flowlauncher.com/docs/#/how-to-create-a-theme";
public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438";
public string SelectedTheme
{
get => Settings.Theme;
set
{
ThemeManager.Instance.ChangeTheme(value);
if (ThemeManager.Instance.BlurEnabled && Settings.UseDropShadowEffect)
DropShadowEffect = false;
}
}
public bool DropShadowEffect
{
get => Settings.UseDropShadowEffect;
set
{
if (ThemeManager.Instance.BlurEnabled && value)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed"));
return;
}
if (value)
{
ThemeManager.Instance.AddDropShadowEffectToCurrentTheme();
}
else
{
ThemeManager.Instance.RemoveDropShadowEffectFromCurrentTheme();
}
Settings.UseDropShadowEffect = value;
}
}
public List<string> Themes =>
ThemeManager.Instance.LoadAvailableThemes().Select(Path.GetFileNameWithoutExtension).ToList();
public class ColorScheme
{
public string Display { get; set; }
public ColorSchemes Value { get; set; }
}
public List<ColorScheme> ColorSchemes
{
get
{
List<ColorScheme> modes = new List<ColorScheme>();
var enums = (ColorSchemes[])Enum.GetValues(typeof(ColorSchemes));
foreach (var e in enums)
{
var key = $"ColorScheme{e}";
var display = InternationalizationManager.Instance.GetTranslation(key);
var m = new ColorScheme { Display = display, Value = e, };
modes.Add(m);
}
return modes;
}
}
public List<string> TimeFormatList { get; } = new()
{
"h:mm",
"hh:mm",
"H:mm",
"HH:mm",
"tt h:mm",
"tt hh:mm",
"h:mm tt",
"hh:mm tt",
"hh:mm:ss tt",
"HH:mm:ss"
};
public List<string> DateFormatList { get; } = new()
{
"MM'/'dd dddd",
"MM'/'dd ddd",
"MM'/'dd",
"MM'-'dd",
"MMMM', 'dd",
"dd'/'MM",
"dd'-'MM",
"ddd MM'/'dd",
"dddd MM'/'dd",
"dddd",
"ddd dd'/'MM",
"dddd dd'/'MM",
"dddd dd', 'MMMM",
"dd', 'MMMM"
};
public string TimeFormat
{
get => Settings.TimeFormat;
set => Settings.TimeFormat = value;
}
public string DateFormat
{
get => Settings.DateFormat;
set => Settings.DateFormat = value;
}
public string ClockText => DateTime.Now.ToString(TimeFormat, Culture);
public string DateText => DateTime.Now.ToString(DateFormat, Culture);
public double WindowWidthSize
{
get => Settings.WindowSize;
set => Settings.WindowSize = value;
}
public bool UseGlyphIcons
{
get => Settings.UseGlyphIcons;
set => Settings.UseGlyphIcons = value;
}
public bool UseAnimation
{
get => Settings.UseAnimation;
set => Settings.UseAnimation = value;
}
public class AnimationSpeed
{
public string Display { get; set; }
public AnimationSpeeds Value { get; set; }
}
public List<AnimationSpeed> AnimationSpeeds
{
get
{
List<AnimationSpeed> speeds = new List<AnimationSpeed>();
var enums = (AnimationSpeeds[])Enum.GetValues(typeof(AnimationSpeeds));
foreach (var e in enums)
{
var key = $"AnimationSpeed{e}";
var display = InternationalizationManager.Instance.GetTranslation(key);
var m = new AnimationSpeed { Display = display, Value = e, };
speeds.Add(m);
}
return speeds;
}
}
public bool UseSound
{
get => Settings.UseSound;
set => Settings.UseSound = value;
}
public double SoundEffectVolume
{
get => Settings.SoundVolume;
set => Settings.SoundVolume = value;
}
public bool UseClock
{
get => Settings.UseClock;
set => Settings.UseClock = value;
}
public bool UseDate
{
get => Settings.UseDate;
set => Settings.UseDate = value;
}
public Brush PreviewBackground
{
get
{
var wallpaper = WallpaperPathRetrieval.GetWallpaperPath();
if (wallpaper is not null && File.Exists(wallpaper))
{
var memStream = new MemoryStream(File.ReadAllBytes(wallpaper));
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = memStream;
bitmap.DecodePixelWidth = 800;
bitmap.DecodePixelHeight = 600;
bitmap.EndInit();
return new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
}
var wallpaperColor = WallpaperPathRetrieval.GetWallpaperColor();
return new SolidColorBrush(wallpaperColor);
}
}
public ResultsViewModel PreviewResults
{
get
{
var results = new List<Result>
{
new Result
{
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"),
IcoPath = Path.Combine(
Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png"
)
},
new Result
{
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"),
IcoPath = Path.Combine(
Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png"
)
},
new Result
{
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"),
IcoPath = Path.Combine(
Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.Program\Images\program.png"
)
},
new Result
{
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"),
IcoPath = Path.Combine(
Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png"
)
}
};
var vm = new ResultsViewModel(Settings);
vm.AddResults(results, "PREVIEW");
return vm;
}
}
public FontFamily SelectedQueryBoxFont
{
get
{
var fontExists = Fonts.SystemFontFamilies.Any(
fontFamily =>
fontFamily.FamilyNames.Values != null &&
fontFamily.FamilyNames.Values.Contains(Settings.QueryBoxFont)
);
return fontExists switch
{
true => new FontFamily(Settings.QueryBoxFont),
_ => new FontFamily("Segoe UI")
};
}
set
{
Settings.QueryBoxFont = value.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme);
}
}
public FamilyTypeface SelectedQueryBoxFontFaces
{
get
{
var typeface = SyntaxSugars.CallOrRescueDefault(
() => SelectedQueryBoxFont.ConvertFromInvariantStringsOrNormal(
Settings.QueryBoxFontStyle,
Settings.QueryBoxFontWeight,
Settings.QueryBoxFontStretch
)
);
return typeface;
}
set
{
Settings.QueryBoxFontStretch = value.Stretch.ToString();
Settings.QueryBoxFontWeight = value.Weight.ToString();
Settings.QueryBoxFontStyle = value.Style.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme);
}
}
public FontFamily SelectedResultFont
{
get
{
var fontExists = Fonts.SystemFontFamilies.Any(
fontFamily =>
fontFamily.FamilyNames.Values != null &&
fontFamily.FamilyNames.Values.Contains(Settings.ResultFont)
);
return fontExists switch
{
true => new FontFamily(Settings.ResultFont),
_ => new FontFamily("Segoe UI")
};
}
set
{
Settings.ResultFont = value.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme);
}
}
public FamilyTypeface SelectedResultFontFaces
{
get
{
var typeface = SyntaxSugars.CallOrRescueDefault(
() => SelectedResultFont.ConvertFromInvariantStringsOrNormal(
Settings.ResultFontStyle,
Settings.ResultFontWeight,
Settings.ResultFontStretch
)
);
return typeface;
}
set
{
Settings.ResultFontStretch = value.Stretch.ToString();
Settings.ResultFontWeight = value.Weight.ToString();
Settings.ResultFontStyle = value.Style.ToString();
ThemeManager.Instance.ChangeTheme(Settings.Theme);
}
}
public string ThemeImage => Constant.QueryTextBoxIconImagePath;
public SettingsPaneThemeViewModel(Settings settings)
{
Settings = settings;
}
}

View file

@ -5,11 +5,12 @@
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:flowlauncher="clr-namespace:Flow.Launcher"
xmlns:local="clr-namespace:Flow.Launcher.SettingPages.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure"
xmlns:viewModels="clr-namespace:Flow.Launcher.SettingPages.ViewModels"
Title="Theme"
d:DataContext="{d:DesignInstance viewModels:SettingsPaneThemeViewModel}"
d:DesignHeight="450"
d:DesignWidth="800"
Style="{DynamicResource SettingPageBasic}"
@ -202,8 +203,10 @@
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="Transparent" />
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
<Setter Property="HorizontalContentAlignment"
Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
<Setter Property="VerticalContentAlignment"
Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
<Setter Property="Padding" Value="0,0,0,0" />
<Setter Property="Margin" Value="4" />
<Setter Property="Template">
@ -230,12 +233,15 @@
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource ThemeHoverButton}" />
<Setter TargetName="Bd" Property="Background"
Value="{DynamicResource ThemeHoverButton}" />
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource ToggleSwitchFillOn}" />
<Setter TargetName="Bd" Property="Background"
Value="{DynamicResource ToggleSwitchFillOn}" />
<Setter TargetName="Bd2" Property="BorderThickness" Value="0" />
<Setter TargetName="Bd2" Property="TextElement.Foreground" Value="{DynamicResource Color02B}" />
<Setter TargetName="Bd2" Property="TextElement.Foreground"
Value="{DynamicResource Color02B}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@ -281,7 +287,8 @@
Margin="10"
HorizontalAlignment="Right"
DockPanel.Dock="Top">
<Hyperlink NavigateUri="{Binding ThemeGallery, Mode=OneWay}">
<Hyperlink NavigateUri="{Binding LinkThemeGallery, Mode=OneWay}"
RequestNavigate="Hyperlink_OnRequestNavigate">
<Run Text="{DynamicResource browserMoreThemes}" />
</Hyperlink>
</TextBlock>
@ -297,7 +304,6 @@
<Border
Margin="0,30,0,0"
Padding="0"
CornerRadius="5"
Style="{DynamicResource SettingGroupBox}">
<StackPanel Orientation="Vertical">
<Border
@ -306,8 +312,10 @@
Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource useGlyphUI}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource useGlyphUIEffect}" />
<TextBlock Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource useGlyphUI}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}"
Text="{DynamicResource useGlyphUIEffect}" />
</StackPanel>
<ui:ToggleSwitch
Grid.Row="0"
@ -429,7 +437,6 @@
<Border
Margin="0,24,0,12"
Padding="0"
CornerRadius="5"
Style="{DynamicResource SettingGroupBox}">
<StackPanel Orientation="Vertical">
<Border
@ -438,7 +445,8 @@
Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource Clock}" />
<TextBlock Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource Clock}" />
</StackPanel>
<StackPanel
Grid.Row="0"
@ -451,8 +459,6 @@
Foreground="{DynamicResource Color04B}"
Text="{Binding ClockText}" />
<ComboBox
x:Name="TimeFormat"
Grid.Column="2"
MinWidth="180"
Margin="0,0,18,0"
VerticalAlignment="Center"
@ -480,7 +486,8 @@
Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource Date}" />
<TextBlock Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource Date}" />
</StackPanel>
<StackPanel
Grid.Row="0"
@ -493,8 +500,6 @@
Foreground="{DynamicResource Color04B}"
Text="{Binding DateText}" />
<ComboBox
x:Name="DateFormat"
Grid.Column="2"
MinWidth="180"
Margin="0,0,18,0"
VerticalAlignment="Center"
@ -518,7 +523,6 @@
<Border
Margin="0,12,0,12"
Padding="0"
CornerRadius="5"
Style="{DynamicResource SettingGroupBox}">
<StackPanel Orientation="Vertical">
<Border
@ -527,8 +531,10 @@
Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource Animation}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource AnimationTip}" />
<TextBlock Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource Animation}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}"
Text="{DynamicResource AnimationTip}" />
</StackPanel>
<ui:ToggleSwitch
x:Name="Animation"
@ -552,7 +558,8 @@
<Style BasedOn="{StaticResource SettingGroupBox}" TargetType="Border">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Animation, Path=IsOn}" Value="True">
<DataTrigger Binding="{Binding ElementName=Animation, Path=IsOn}"
Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
@ -561,8 +568,10 @@
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource AnimationSpeed}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource AnimationSpeedTip}" />
<TextBlock Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource AnimationSpeed}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}"
Text="{DynamicResource AnimationSpeedTip}" />
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<ComboBox
@ -580,7 +589,9 @@
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=AnimationSpeed, Path=SelectedValue}" Value="{x:Static userSettings:AnimationSpeeds.Custom}">
<DataTrigger
Binding="{Binding ElementName=AnimationSpeed, Path=SelectedValue}"
Value="{x:Static userSettings:AnimationSpeeds.Custom}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
@ -604,7 +615,6 @@
<Border
Margin="0,12,0,12"
Padding="0"
CornerRadius="5"
Style="{DynamicResource SettingGroupBox}">
<StackPanel Orientation="Vertical">
<Border
@ -613,8 +623,10 @@
Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource SoundEffect}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource SoundEffectTip}" />
<TextBlock Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource SoundEffect}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}"
Text="{DynamicResource SoundEffectTip}" />
</StackPanel>
<ui:ToggleSwitch
x:Name="SoundEffect"
@ -638,7 +650,8 @@
<Style BasedOn="{StaticResource SettingGroupBox}" TargetType="Border">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SoundEffect, Path=IsOn}" Value="True">
<DataTrigger Binding="{Binding ElementName=SoundEffect, Path=IsOn}"
Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
@ -647,8 +660,10 @@
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource SoundEffectVolume}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource SoundEffectVolumeTip}" />
<TextBlock Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource SoundEffectVolume}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}"
Text="{DynamicResource SoundEffectVolumeTip}" />
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<TextBlock
@ -681,7 +696,8 @@
<Border Margin="0,12,0,12" Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource ColorScheme}" />
<TextBlock Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource ColorScheme}" />
</StackPanel>
<ComboBox
x:Name="ColorSchemeComboBox"
@ -702,7 +718,8 @@
<Border Margin="0,0,0,0" Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource ThemeFolder}" />
<TextBlock Style="{DynamicResource SettingTitleLabel}"
Text="{DynamicResource ThemeFolder}" />
</StackPanel>
<Button
Grid.Column="2"
@ -721,7 +738,8 @@
Margin="10,10,10,18"
HorizontalAlignment="Right"
DockPanel.Dock="Top">
<Hyperlink NavigateUri="{Binding Theme, Mode=OneWay}">
<Hyperlink NavigateUri="{Binding LinkHowToCreateTheme, Mode=OneWay}"
RequestNavigate="Hyperlink_OnRequestNavigate">
<Run Text="{DynamicResource howToCreateTheme}" />
</Hyperlink>
</TextBlock>

View file

@ -1,23 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Flow.Launcher.SettingPages.ViewModels;
using ModernWpf.Controls;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneTheme : Page
{
public SettingsPaneTheme()
private SettingsPaneThemeViewModel _viewModel = null!;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
InitializeComponent();
if (!IsInitialized)
{
if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings })
throw new ArgumentException($"Settings are required for {nameof(SettingsPaneTheme)}.");
_viewModel = new SettingsPaneThemeViewModel(settings);
DataContext = _viewModel;
InitializeComponent();
}
base.OnNavigatedTo(e);
}
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
{
App.API.OpenUrl(e.Uri);
e.Handled = true;
}
}