mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 18:57:19 +02:00
[New Module] Light Switch (#41987)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request This pull request introduces a new module called "Light Switch" which allows users to automatically switch between light and dark mode on a timer.  <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #1331 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [x] **New binaries:** Added on the required places - [x] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [x] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [x] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: [#5867](https://github.com/MicrosoftDocs/windows-dev-docs-pr/pull/5867) <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments ### Known bugs: - Default settings not saving correctly when switching modes - Issue: Sometimes when you switch from one mode to another, they are supposed to update with new defaults but sometimes this fails for the second variable. Potentially has to do with accessing the settings file while another chunk of code is still updating. - Sometimes the system looks "glitched" when switching themes ### To do: - [x] OOBE page and assets - [x] Logic to disable the chart when no location has been selected - [x] Localization ### How to and what to test Grab the latest installer from the pipeline below for your architecture and install PowerToys from there. - Toggle theme shortcutSystem only, Apps only, Both system and apps selected - Does changing the values on the settings page update the settings file? %LOCALAPPDATA%/Microsoft/PowerToys/LightSwitch/settings.json - Manual mode: System only, Apps only, Both system and apps selected - Sunrise modes: Are the times accurate? - If you manage to let this run through sunset/rise does the theme change? - Set your theme to change within the next minute using manual mode and set your device to sleepOpen your device and login once the time you set has passed. --> Do your settings resync once the next minute ticks after logging back into your device? - Disable the service and ensure the tasks actually ends. - While the module is disabled: - Make sure the shortcut no longer works - Make sure the last time you set doesn't trigger a theme change - Bonus: Toggle GPO Configuration and make sure you are unable to enable the module --------- Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
<ResourceDictionary Source="/SettingsXAML/Styles/InfoBadge.xaml" />
|
||||
<ResourceDictionary Source="/SettingsXAML/Themes/Colors.xaml" />
|
||||
<ResourceDictionary Source="/SettingsXAML/Themes/Generic.xaml" />
|
||||
<ResourceDictionary Source="/SettingsXAML/Controls/Timeline/TimelineStyles.xaml" />
|
||||
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
@@ -417,6 +417,7 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
case "Awake": return typeof(AwakePage);
|
||||
case "CmdNotFound": return typeof(CmdNotFoundPage);
|
||||
case "ColorPicker": return typeof(ColorPickerPage);
|
||||
case "LightSwitch": return typeof(LightSwitchPage);
|
||||
case "FancyZones": return typeof(FancyZonesPage);
|
||||
case "FileLocksmith": return typeof(FileLocksmithPage);
|
||||
case "Run": return typeof(PowerLauncherPage);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{x:Bind ModuleTitle}" />
|
||||
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<ScrollViewer Grid.Row="1" AutomationProperties.AutomationId="PageScrollViewer">
|
||||
<Grid
|
||||
Padding="0,0,20,48"
|
||||
ChildrenTransitions="{StaticResource SettingsCardsAnimations}"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.Controls.Timeline"
|
||||
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:local="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Canvas
|
||||
x:Name="HeaderCanvas"
|
||||
Grid.Row="0"
|
||||
Height="24" />
|
||||
|
||||
<!-- Timeline (bands + ticks + labels) -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{ThemeResource OverlayCornerRadius}">
|
||||
<Canvas
|
||||
x:Name="TimelineCanvas"
|
||||
Height="36"
|
||||
Loaded="TimelineCanvas_Loaded" />
|
||||
</Border>
|
||||
|
||||
<!-- Below-chart annotations (sunrise/sunset panels + major labels) -->
|
||||
<Canvas
|
||||
x:Name="AnnotationCanvas"
|
||||
Grid.Row="2"
|
||||
Height="32" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,658 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Automation;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Shapes;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
{
|
||||
public sealed partial class Timeline : UserControl
|
||||
{
|
||||
public TimeSpan StartTime
|
||||
{
|
||||
get => (TimeSpan)GetValue(StartTimeProperty);
|
||||
set => SetValue(StartTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty StartTimeProperty = DependencyProperty.Register(nameof(StartTime), typeof(TimeSpan), typeof(Timeline), new PropertyMetadata(defaultValue: new TimeSpan(22, 0, 0), OnTimeChanged));
|
||||
|
||||
public TimeSpan EndTime
|
||||
{
|
||||
get => (TimeSpan)GetValue(EndTimeProperty);
|
||||
set => SetValue(EndTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty EndTimeProperty = DependencyProperty.Register(nameof(EndTime), typeof(TimeSpan), typeof(Timeline), new PropertyMetadata(defaultValue: new TimeSpan(7, 0, 0), OnTimeChanged));
|
||||
|
||||
public TimeSpan? Sunrise
|
||||
{
|
||||
get => (TimeSpan?)GetValue(SunriseProperty);
|
||||
set => SetValue(SunriseProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SunriseProperty = DependencyProperty.Register(nameof(Sunrise), typeof(TimeSpan), typeof(Timeline), new PropertyMetadata(defaultValue: null, OnTimeChanged));
|
||||
|
||||
public TimeSpan? Sunset
|
||||
{
|
||||
get => (TimeSpan?)GetValue(SunsetProperty);
|
||||
set => SetValue(SunsetProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SunsetProperty = DependencyProperty.Register(nameof(Sunset), typeof(TimeSpan), typeof(Timeline), new PropertyMetadata(defaultValue: null, OnTimeChanged));
|
||||
|
||||
private readonly List<int> _tickHours = new();
|
||||
|
||||
// Locale 24h/12h
|
||||
private readonly bool _is24h = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.Contains('H');
|
||||
|
||||
// Visuals
|
||||
private readonly List<Line> _ticks = new();
|
||||
private readonly List<TextBlock> _majorTickBottomLabels = new(); // 00,06,12,18,24 (below)
|
||||
|
||||
private readonly List<Border> _darkRects = new(); // up to 2 (wrap)
|
||||
private readonly List<Border> _lightRects = new(); // up to 2 (complement)
|
||||
|
||||
private TextBlock _startEdgeLabel; // top-of-chart
|
||||
private TextBlock _endEdgeLabel;
|
||||
|
||||
private Line _sunriseTick;
|
||||
private Line _sunsetTick;
|
||||
|
||||
// Add/replace these constants (top of your class)
|
||||
private const int TickHourStep = 2; // <-- every 2 hours
|
||||
|
||||
private StackPanel _sunrisePanel; // icon + time (below chart)
|
||||
private StackPanel _sunsetPanel;
|
||||
|
||||
public Timeline()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.Loaded += Timeline_Loaded;
|
||||
this.IsEnabledChanged += Timeline_IsEnabledChanged;
|
||||
}
|
||||
|
||||
private void Timeline_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckEnabledState();
|
||||
}
|
||||
|
||||
private void Timeline_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
CheckEnabledState();
|
||||
}
|
||||
|
||||
private void CheckEnabledState()
|
||||
{
|
||||
if (IsEnabled)
|
||||
{
|
||||
this.Opacity = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Opacity = 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((Timeline)d).Setup();
|
||||
}
|
||||
|
||||
private void Setup()
|
||||
{
|
||||
EnsureBands();
|
||||
EnsureTicks();
|
||||
EnsureStartEndEdgeLabels();
|
||||
EnsureSunriseSunsetTicks();
|
||||
EnsureSunPanels();
|
||||
EnsureMajorTickLabels();
|
||||
UpdateAll();
|
||||
}
|
||||
|
||||
private void TimelineCanvas_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// SizeChanged wiring here (as requested)
|
||||
HeaderCanvas.SizeChanged += (_, __) => UpdateAll();
|
||||
TimelineCanvas.SizeChanged += (_, __) => UpdateAll();
|
||||
AnnotationCanvas.SizeChanged += (_, __) => UpdateAll();
|
||||
Setup();
|
||||
}
|
||||
|
||||
private void UpdateAll()
|
||||
{
|
||||
UpdateBandsLayout();
|
||||
UpdateTicksLayout();
|
||||
UpdateStartEndEdgeLabelsLayout();
|
||||
UpdateSunriseSunsetTicksLayout();
|
||||
UpdateSunPanelsLayout();
|
||||
UpdateMajorTickLabelsLayout();
|
||||
AutomationProperties.SetHelpText(
|
||||
this,
|
||||
$"Start={StartTime};End={EndTime};Sunrise={Sunrise};Sunset={Sunset}");
|
||||
}
|
||||
|
||||
// ===== Ticks =====
|
||||
private void EnsureTicks()
|
||||
{
|
||||
if (_ticks.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_tickHours.Clear();
|
||||
|
||||
// Build ticks at 0,2,4,...,24 but skip the first/last MAJOR ticks (0 and 24)
|
||||
for (int hour = 0; hour <= 24; hour += TickHourStep)
|
||||
{
|
||||
bool isMajor = hour % 6 == 0;
|
||||
if (isMajor && (hour == 0 || hour == 24))
|
||||
{
|
||||
continue; // skip first/last major ticks
|
||||
}
|
||||
|
||||
var line = new Line
|
||||
{
|
||||
Style = (Style)Application.Current.Resources[isMajor ? "MajorHourTickStyle" : "HourTickStyle"],
|
||||
};
|
||||
|
||||
Canvas.SetZIndex(line, 0); // above bands (adjust if needed)
|
||||
|
||||
_ticks.Add(line);
|
||||
_tickHours.Add(hour);
|
||||
|
||||
// If you actually want these IN the chart, use TimelineCanvas instead:
|
||||
AnnotationCanvas.Children.Add(line); // or TimelineCanvas.Children.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTicksLayout()
|
||||
{
|
||||
double w = TimelineCanvas.ActualWidth;
|
||||
double h = TimelineCanvas.ActualHeight; // keeping your offset
|
||||
if (w <= 0 || h <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double minorLen = h * 0.1;
|
||||
double majorLen = h * 0.2;
|
||||
|
||||
for (int i = 0; i < _ticks.Count; i++)
|
||||
{
|
||||
int hour = _tickHours[i];
|
||||
double x = Math.Round((hour / 24.0) * w);
|
||||
|
||||
var line = _ticks[i];
|
||||
double len = (hour % 6 == 0) ? majorLen : minorLen;
|
||||
|
||||
line.X1 = x;
|
||||
line.Y1 = 0;
|
||||
line.X2 = x;
|
||||
line.Y2 = len;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Bands (Dark + Light) =====
|
||||
private void EnsureBands()
|
||||
{
|
||||
if (_darkRects.Count == 0)
|
||||
{
|
||||
_darkRects.Add(MakeBandRect(isDark: false));
|
||||
_darkRects.Add(MakeBandRect(isDark: false));
|
||||
}
|
||||
|
||||
if (_lightRects.Count == 0)
|
||||
{
|
||||
_lightRects.Add(MakeBandRect(isDark: true));
|
||||
_lightRects.Add(MakeBandRect(isDark: true));
|
||||
}
|
||||
}
|
||||
|
||||
private Border MakeBandRect(bool isDark)
|
||||
{
|
||||
var r = new Border();
|
||||
if (isDark)
|
||||
{
|
||||
r.Style = (Style)Application.Current.Resources["DarkBandStyle"];
|
||||
FontIcon icon = new FontIcon();
|
||||
icon.Style = (Style)Application.Current.Resources["DarkBandIconStyle"];
|
||||
r.Child = icon;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.Style = (Style)Application.Current.Resources["LightBandStyle"];
|
||||
}
|
||||
|
||||
Canvas.SetZIndex(r, 5); // below ticks/labels
|
||||
TimelineCanvas.Children.Add(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
private void UpdateBandsLayout()
|
||||
{
|
||||
double w = TimelineCanvas.ActualWidth;
|
||||
double h = TimelineCanvas.ActualHeight;
|
||||
if (w <= 0 || h <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var r in _darkRects)
|
||||
{
|
||||
r.Height = h;
|
||||
Canvas.SetTop(r, 0);
|
||||
}
|
||||
|
||||
foreach (var r in _lightRects)
|
||||
{
|
||||
r.Height = h;
|
||||
Canvas.SetTop(r, 0);
|
||||
}
|
||||
|
||||
var darkRanges = ToRanges(StartTime, EndTime); // 1 or 2 segments
|
||||
var lightRanges = ComplementRanges(darkRanges); // 0..2
|
||||
|
||||
LayoutRangeRects(_darkRects, darkRanges, w);
|
||||
LayoutRangeRects(_lightRects, lightRanges, w);
|
||||
}
|
||||
|
||||
private static void LayoutRangeRects(List<Border> rects, List<(TimeSpan Start, TimeSpan End)> ranges, double width)
|
||||
{
|
||||
for (int i = 0; i < rects.Count; i++)
|
||||
{
|
||||
if (i < ranges.Count)
|
||||
{
|
||||
var (start, end) = ranges[i];
|
||||
double x = Math.Round((start.TotalHours / 24.0) * width);
|
||||
double x2 = Math.Round((end.TotalHours / 24.0) * width);
|
||||
|
||||
var r = rects[i];
|
||||
Canvas.SetLeft(r, x);
|
||||
r.Width = Math.Max(0, x2 - x);
|
||||
r.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
rects[i].Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<(TimeSpan Start, TimeSpan End)> ToRanges(TimeSpan start, TimeSpan end)
|
||||
{
|
||||
// Full day
|
||||
if (start == end)
|
||||
{
|
||||
return new() { (TimeSpan.Zero, TimeSpan.FromHours(24)) };
|
||||
}
|
||||
|
||||
if (start < end)
|
||||
{
|
||||
return new() { (start, end) };
|
||||
}
|
||||
|
||||
// Wraps midnight
|
||||
return new()
|
||||
{
|
||||
(start, TimeSpan.FromHours(24)),
|
||||
(TimeSpan.Zero, end),
|
||||
};
|
||||
}
|
||||
|
||||
private static List<(TimeSpan Start, TimeSpan End)> ComplementRanges(List<(TimeSpan Start, TimeSpan End)> dark)
|
||||
{
|
||||
var res = new List<(TimeSpan, TimeSpan)>();
|
||||
|
||||
// If dark covers the full day, there is no light
|
||||
if (dark.Count == 1 && dark[0].Start == TimeSpan.Zero && dark[0].End == TimeSpan.FromHours(24))
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
if (dark.Count == 1)
|
||||
{
|
||||
var (ds, de) = dark[0];
|
||||
if (ds > TimeSpan.Zero)
|
||||
{
|
||||
res.Add((TimeSpan.Zero, ds));
|
||||
}
|
||||
|
||||
if (de < TimeSpan.FromHours(24))
|
||||
{
|
||||
res.Add((de, TimeSpan.FromHours(24)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// dark[0] = [a,24), dark[1] = [0,b) => single light [b,a)
|
||||
var a = dark[0].Start;
|
||||
var b = dark[1].End;
|
||||
res.Add((b, a));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// ===== Start & End labels (TOP of chart, ABOVE rectangles) =====
|
||||
private void EnsureStartEndEdgeLabels()
|
||||
{
|
||||
if (_startEdgeLabel == null)
|
||||
{
|
||||
_startEdgeLabel = new TextBlock { Style = (Style)Application.Current.Resources["EdgeLabelStyle"] };
|
||||
HeaderCanvas.Children.Add(_startEdgeLabel);
|
||||
Canvas.SetZIndex(_startEdgeLabel, 25);
|
||||
}
|
||||
|
||||
if (_endEdgeLabel == null)
|
||||
{
|
||||
_endEdgeLabel = new TextBlock { Style = (Style)Application.Current.Resources["EdgeLabelStyle"] };
|
||||
HeaderCanvas.Children.Add(_endEdgeLabel);
|
||||
Canvas.SetZIndex(_endEdgeLabel, 25);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStartEndEdgeLabelsLayout()
|
||||
{
|
||||
double w = TimelineCanvas.ActualWidth;
|
||||
if (w <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_startEdgeLabel.Text = TimeSpanHelper.Convert(StartTime);
|
||||
_endEdgeLabel.Text = TimeSpanHelper.Convert(EndTime);
|
||||
|
||||
PlaceTopLabelAtTime(_startEdgeLabel, StartTime, w);
|
||||
PlaceTopLabelAtTime(_endEdgeLabel, EndTime, w);
|
||||
}
|
||||
|
||||
private void PlaceTopLabelAtTime(TextBlock tb, TimeSpan t, double timelineWidth)
|
||||
{
|
||||
double x = Math.Round((t.TotalHours / 24.0) * timelineWidth);
|
||||
double textW = MeasureTextWidth(tb);
|
||||
double desiredLeft = x - (textW / 2.0);
|
||||
|
||||
Canvas.SetLeft(tb, Clamp(desiredLeft, 0, timelineWidth - textW));
|
||||
Canvas.SetTop(tb, 0);
|
||||
tb.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
// ===== Sunrise/Sunset ticks on chart =====
|
||||
private void EnsureSunriseSunsetTicks()
|
||||
{
|
||||
if (_sunriseTick == null)
|
||||
{
|
||||
_sunriseTick = new Line { Style = (Style)Application.Current.Resources["SunRiseMarkerTickStyle"] };
|
||||
TimelineCanvas.Children.Add(_sunriseTick);
|
||||
Canvas.SetZIndex(_sunriseTick, 12);
|
||||
}
|
||||
|
||||
if (_sunsetTick == null)
|
||||
{
|
||||
_sunsetTick = new Line { Style = (Style)Application.Current.Resources["SunSetMarkerTickStyle"] };
|
||||
TimelineCanvas.Children.Add(_sunsetTick);
|
||||
Canvas.SetZIndex(_sunsetTick, 12);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSunriseSunsetTicksLayout()
|
||||
{
|
||||
double w = TimelineCanvas.ActualWidth;
|
||||
double h = TimelineCanvas.ActualHeight + 24;
|
||||
if (w <= 0 || h <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void Place(Line tick, TimeSpan t)
|
||||
{
|
||||
double x = Math.Round((t.TotalHours / 24.0) * w);
|
||||
tick.X1 = x;
|
||||
tick.X2 = x;
|
||||
tick.Y1 = 0;
|
||||
tick.Y2 = h;
|
||||
}
|
||||
|
||||
if (_sunriseTick != null)
|
||||
{
|
||||
if (Sunrise.HasValue)
|
||||
{
|
||||
Place(_sunriseTick, Sunrise.Value);
|
||||
_sunriseTick.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
_sunriseTick.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (_sunsetTick != null)
|
||||
{
|
||||
if (Sunset.HasValue)
|
||||
{
|
||||
Place(_sunsetTick, Sunset.Value);
|
||||
_sunsetTick.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
_sunsetTick.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Sunrise/Sunset panels (below chart) =====
|
||||
private void EnsureSunPanels()
|
||||
{
|
||||
if (_sunrisePanel == null)
|
||||
{
|
||||
_sunrisePanel = MakeSunPanel("\uEC8A");
|
||||
AnnotationCanvas.Children.Add(_sunrisePanel);
|
||||
}
|
||||
|
||||
if (_sunsetPanel == null)
|
||||
{
|
||||
_sunsetPanel = MakeSunPanel("\uED3A");
|
||||
AnnotationCanvas.Children.Add(_sunsetPanel);
|
||||
}
|
||||
}
|
||||
|
||||
private StackPanel MakeSunPanel(string iconEmoji)
|
||||
{
|
||||
var icon = new FontIcon { Glyph = iconEmoji, Style = (Style)Application.Current.Resources["SunIconStyle"] };
|
||||
var sp = new StackPanel { Orientation = Orientation.Vertical, Spacing = 2 };
|
||||
sp.Children.Add(icon);
|
||||
return sp;
|
||||
}
|
||||
|
||||
private void UpdateSunPanelsLayout()
|
||||
{
|
||||
double timelineW = TimelineCanvas.ActualWidth;
|
||||
double annotationW = AnnotationCanvas.ActualWidth;
|
||||
if (annotationW <= 0)
|
||||
{
|
||||
annotationW = timelineW;
|
||||
}
|
||||
|
||||
if (timelineW <= 0 || annotationW <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void Place(StackPanel sp, TimeSpan t)
|
||||
{
|
||||
double panelW = MeasureElementWidth(sp);
|
||||
double xTimeline = Math.Round((t.TotalHours / 24.0) * timelineW);
|
||||
double left = Clamp(xTimeline - (panelW / 2.0), 0, annotationW - panelW);
|
||||
Canvas.SetLeft(sp, left);
|
||||
Canvas.SetTop(sp, 8);
|
||||
}
|
||||
|
||||
if (_sunrisePanel != null)
|
||||
{
|
||||
if (Sunrise.HasValue)
|
||||
{
|
||||
ToolTipService.SetToolTip(_sunrisePanel, $"Sunrise: {TimeSpanHelper.Convert(Sunrise.Value)}");
|
||||
_sunrisePanel.Visibility = Visibility.Visible;
|
||||
Place(_sunrisePanel, Sunrise.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToolTipService.SetToolTip(_sunrisePanel, null);
|
||||
_sunrisePanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (_sunsetPanel != null)
|
||||
{
|
||||
if (Sunset.HasValue)
|
||||
{
|
||||
ToolTipService.SetToolTip(_sunsetPanel, $"Sunset: {TimeSpanHelper.Convert(Sunset.Value)}");
|
||||
_sunsetPanel.Visibility = Visibility.Visible;
|
||||
Place(_sunsetPanel, Sunset.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToolTipService.SetToolTip(_sunsetPanel, null);
|
||||
_sunsetPanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Major labels BELOW chart (00,06,12,18,24) =====
|
||||
private void EnsureMajorTickLabels()
|
||||
{
|
||||
if (_majorTickBottomLabels.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Includes 24:00 at end
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var tb = new TextBlock { Style = (Style)Application.Current.Resources["MajorTickLabelStyle"] };
|
||||
Canvas.SetZIndex(tb, 5); // on annotation canvas
|
||||
_majorTickBottomLabels.Add(tb);
|
||||
AnnotationCanvas.Children.Add(tb);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMajorTickLabelsLayout()
|
||||
{
|
||||
double timelineW = TimelineCanvas.ActualWidth;
|
||||
double annotationW = AnnotationCanvas.ActualWidth;
|
||||
if (annotationW <= 0)
|
||||
{
|
||||
annotationW = timelineW;
|
||||
}
|
||||
|
||||
if (timelineW <= 0 || annotationW <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int[] hours = { 0, 6, 12, 18, 24 };
|
||||
|
||||
// 1) Place labels first
|
||||
for (int i = 0; i < hours.Length; i++)
|
||||
{
|
||||
var tb = _majorTickBottomLabels[i];
|
||||
var t = TimeSpan.FromHours(hours[i]);
|
||||
tb.Text = TimeSpanHelper.Convert(t);
|
||||
|
||||
double xTimeline = Math.Round((t.TotalHours / 24.0) * timelineW);
|
||||
double textW = MeasureTextWidth(tb);
|
||||
double left = xTimeline - (textW / 2.0);
|
||||
|
||||
// Middle ones (06, 12) exact center; edges clamp inside canvas
|
||||
if (i == 1 || i == 2)
|
||||
{
|
||||
Canvas.SetLeft(tb, left);
|
||||
}
|
||||
else
|
||||
{
|
||||
Canvas.SetLeft(tb, Clamp(left, 0, annotationW - textW));
|
||||
}
|
||||
|
||||
Canvas.SetTop(tb, 8); // your existing baseline below chart
|
||||
tb.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
// 2) Compute sunrise/sunset occupied horizontal ranges (if present)
|
||||
(double Left, double Right)? sunriseBounds = null;
|
||||
(double Left, double Right)? sunsetBounds = null;
|
||||
|
||||
if (Sunrise.HasValue && _sunrisePanel != null)
|
||||
{
|
||||
sunriseBounds = GetAnnotationBoundsForTime(Sunrise.Value, timelineW, annotationW, _sunrisePanel);
|
||||
}
|
||||
|
||||
if (Sunset.HasValue && _sunsetPanel != null)
|
||||
{
|
||||
sunsetBounds = GetAnnotationBoundsForTime(Sunset.Value, timelineW, annotationW, _sunsetPanel);
|
||||
}
|
||||
|
||||
// 3) Hide any label that intersects the sunrise/sunset panel bounds
|
||||
for (int i = 0; i < hours.Length; i++)
|
||||
{
|
||||
var tb = _majorTickBottomLabels[i];
|
||||
if (tb.Visibility != Visibility.Visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var lbl = GetLabelBounds(tb);
|
||||
|
||||
bool hide =
|
||||
(sunriseBounds.HasValue && Intersects(lbl, sunriseBounds.Value)) ||
|
||||
(sunsetBounds.HasValue && Intersects(lbl, sunsetBounds.Value)); // include sunset too; remove if you only want sunrise
|
||||
|
||||
tb.Visibility = hide ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Utilities =====
|
||||
private static double Clamp(double v, double min, double max) => Math.Max(min, Math.Min(max, v));
|
||||
|
||||
private static double MeasureElementWidth(FrameworkElement el)
|
||||
{
|
||||
el.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
return el.DesiredSize.Width;
|
||||
}
|
||||
|
||||
private static double MeasureTextWidth(TextBlock tb)
|
||||
{
|
||||
tb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
return tb.DesiredSize.Width;
|
||||
}
|
||||
|
||||
private static bool Intersects((double Left, double Right) a, (double Left, double Right) b, double pad = 4)
|
||||
{
|
||||
// Horizontal overlap with padding
|
||||
return !(a.Right + pad <= b.Left || b.Right + pad <= a.Left);
|
||||
}
|
||||
|
||||
private (double Left, double Right) GetAnnotationBoundsForTime(TimeSpan t, double timelineW, double annotationW, FrameworkElement element)
|
||||
{
|
||||
// Compute the *actual* left/right the panel will occupy in AnnotationCanvas
|
||||
double panelW = MeasureElementWidth(element);
|
||||
double xTimeline = Math.Round((t.TotalHours / 24.0) * timelineW);
|
||||
double left = Clamp(xTimeline - (panelW / 2.0), 0, annotationW - panelW);
|
||||
return (left, left + panelW);
|
||||
}
|
||||
|
||||
private (double Left, double Right) GetLabelBounds(TextBlock tb)
|
||||
{
|
||||
double w = MeasureTextWidth(tb);
|
||||
double left = Canvas.GetLeft(tb);
|
||||
return (left, left + w);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style x:Key="HourTickStyle" TargetType="Line">
|
||||
<Setter Property="Stroke" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
<Setter Property="StrokeThickness" Value="1" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="MajorHourTickStyle"
|
||||
BasedOn="{StaticResource HourTickStyle}"
|
||||
TargetType="Line">
|
||||
<Setter Property="StrokeThickness" Value="2" />
|
||||
<Setter Property="Stroke" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SunRiseMarkerTickStyle" TargetType="Line">
|
||||
<Setter Property="Stroke" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
<Setter Property="StrokeThickness" Value="1" />
|
||||
<Setter Property="StrokeDashArray" Value="2,2" />
|
||||
</Style>
|
||||
|
||||
|
||||
<Style x:Key="SunSetMarkerTickStyle" TargetType="Line">
|
||||
<Setter Property="Stroke" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
<Setter Property="StrokeThickness" Value="1" />
|
||||
<Setter Property="StrokeDashArray" Value="2,2" />
|
||||
</Style>
|
||||
|
||||
<!-- ===== Text / Labels ===== -->
|
||||
<Style x:Key="EdgeLabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource AccentTextFillColorPrimaryBrush}" />
|
||||
<Setter Property="IsHitTestVisible" Value="False" />
|
||||
</Style>
|
||||
|
||||
<!-- Below-chart labels for 00/06/12/18/24 -->
|
||||
<Style x:Key="MajorTickLabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
<Setter Property="IsHitTestVisible" Value="False" />
|
||||
</Style>
|
||||
|
||||
<!-- Sunrise/Sunset panel styles -->
|
||||
<Style x:Key="SunIconStyle" TargetType="FontIcon">
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<!-- ===== Bands ===== -->
|
||||
<Style x:Key="DarkBandStyle" TargetType="Border">
|
||||
<!--<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Opacity="0.6" Color="{ThemeResource SystemAccentColorDark1}"/>
|
||||
</Setter.Value>
|
||||
</Setter>-->
|
||||
<Setter Property="Background" Value="{ThemeResource AccentFillColorTertiaryBrush}" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
<Setter Property="Padding" Value="4" />
|
||||
<Setter Property="ToolTipService.ToolTip" Value="Dark mode" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LightBandStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
|
||||
<Setter Property="ToolTipService.ToolTip" Value="Light mode" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DarkBandIconStyle" TargetType="FontIcon">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Glyph" Value="" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextOnAccentFillColorPrimaryBrush}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="LightBandIconStyle"
|
||||
BasedOn="{StaticResource DarkBandIconStyle}"
|
||||
TargetType="FontIcon">
|
||||
<Setter Property="Glyph" Value="" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeLightSwitch"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<controls:OOBEPageControl x:Uid="Oobe_LightSwitch" HeroImage="ms-appx:///Assets/Settings/Modules/OOBE/LightSwitch.png">
|
||||
<controls:OOBEPageControl.PageContent>
|
||||
<StackPanel Orientation="Vertical" Spacing="12">
|
||||
<TextBlock x:Uid="Oobe_HowToUse" Style="{ThemeResource OobeSubtitleStyle}" />
|
||||
|
||||
<tkcontrols:MarkdownTextBlock x:Uid="Oobe_LightSwitch_HowToUse" />
|
||||
|
||||
<TextBlock x:Uid="Oobe_TipsAndTricks" Style="{ThemeResource OobeSubtitleStyle}" />
|
||||
|
||||
<tkcontrols:MarkdownTextBlock x:Uid="Oobe_LightSwitch_TipsAndTricks" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button x:Uid="OOBE_Settings" Click="SettingsLaunchButton_Click" />
|
||||
|
||||
<HyperlinkButton NavigateUri="https://aka.ms/PowerToysOverview_LightSwitch" Style="{StaticResource TextButtonStyle}">
|
||||
<TextBlock x:Uid="LearnMore_LightSwitch" TextWrapping="Wrap" />
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</controls:OOBEPageControl.PageContent>
|
||||
</controls:OOBEPageControl>
|
||||
</Page>
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
|
||||
using Microsoft.PowerToys.Settings.UI.Views;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
{
|
||||
public sealed partial class OobeLightSwitch : Page
|
||||
{
|
||||
public OobePowerToysModule ViewModel { get; set; }
|
||||
|
||||
public OobeLightSwitch()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.LightSwitch]);
|
||||
}
|
||||
|
||||
private void SettingsLaunchButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (OobeShellPage.OpenMainWindowCallback != null)
|
||||
{
|
||||
OobeShellPage.OpenMainWindowCallback(typeof(LightSwitchPage));
|
||||
}
|
||||
|
||||
ViewModel.LogOpeningSettingsEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,6 +117,10 @@
|
||||
x:Uid="Shell_KeyboardManager"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/KeyboardManager.png}"
|
||||
Tag="KBM" />
|
||||
<NavigationViewItem
|
||||
x:Uid="Shell_LightSwitch"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/LightSwitch.png}"
|
||||
Tag="LightSwitch" />
|
||||
<NavigationViewItem
|
||||
x:Uid="Shell_MouseUtilities"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}"
|
||||
|
||||
@@ -138,6 +138,11 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
ModuleName = "KBM",
|
||||
IsNew = false,
|
||||
});
|
||||
Modules.Insert((int)PowerToysModules.LightSwitch, new OobePowerToysModule()
|
||||
{
|
||||
ModuleName = "LightSwitch",
|
||||
IsNew = true,
|
||||
});
|
||||
Modules.Insert((int)PowerToysModules.MouseUtils, new OobePowerToysModule()
|
||||
{
|
||||
ModuleName = "MouseUtils",
|
||||
@@ -287,6 +292,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
case "Run": NavigationFrame.Navigate(typeof(OobeRun)); break;
|
||||
case "ImageResizer": NavigationFrame.Navigate(typeof(OobeImageResizer)); break;
|
||||
case "KBM": NavigationFrame.Navigate(typeof(OobeKBM)); break;
|
||||
case "LightSwitch": NavigationFrame.Navigate(typeof(OobeLightSwitch)); break;
|
||||
case "PowerRename": NavigationFrame.Navigate(typeof(OobePowerRename)); break;
|
||||
case "QuickAccent": NavigationFrame.Navigate(typeof(OobePowerAccent)); break;
|
||||
case "FileExplorer": NavigationFrame.Navigate(typeof(OobeFileExplorer)); break;
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.Views.LightSwitchPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ViewModel="using:Microsoft.PowerToys.Settings.UI.ViewModels"
|
||||
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
|
||||
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helpers="using:Settings.UI.Library.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
d:DataContext="{d:DesignInstance Type=ViewModel:LightSwitchViewModel}"
|
||||
AutomationProperties.LandmarkType="Main"
|
||||
mc:Ignorable="d">
|
||||
<Page.Resources>
|
||||
<converters:EnumToVisibilityConverter x:Key="EnumToVisibilityConverter" />
|
||||
<converters:TimeSpanToFriendlyTimeConverter x:Key="TimeSpanToFriendlyTimeConverter" />
|
||||
</Page.Resources>
|
||||
<controls:SettingsPageControl
|
||||
x:Uid="LightSwitch"
|
||||
IsTabStop="False"
|
||||
ModuleImageSource="ms-appx:///Assets/Settings/Modules/LightSwitch.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
x:Uid="LightSwitch_EnableSettingsCard"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/LightSwitch.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch AutomationProperties.AutomationId="Toggle_LightSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<controls:SettingsGroup x:Uid="LightSwitch_ShortcutsSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard x:Uid="LightSwitch_ThemeToggle_Shortcut" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<controls:ShortcutControl
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
AllowDisable="True"
|
||||
AutomationProperties.AutomationId="Shortcut_LightSwitch"
|
||||
HotkeySettings="{x:Bind Path=ViewModel.ToggleThemeActivationShortcut, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="LightSwitch_ScheduleSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="LightSwitch_ModeSettingsExpander"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<ComboBox
|
||||
x:Name="ModeSelector"
|
||||
AutomationProperties.AutomationId="ModeSelection_LightSwitch"
|
||||
SelectedValue="{x:Bind ViewModel.ScheduleMode, Mode=TwoWay}"
|
||||
SelectedValuePath="Tag"
|
||||
SelectionChanged="ModeSelector_SelectionChanged">
|
||||
<ComboBoxItem
|
||||
x:Uid="LightSwitch_ModeManual"
|
||||
AutomationProperties.AutomationId="ManualCBItem_LightSwitch"
|
||||
Tag="FixedHours" />
|
||||
<ComboBoxItem
|
||||
x:Uid="LightSwitch_ModeSunsetToSunrise"
|
||||
AutomationProperties.AutomationId="SunCBItem_LightSwitch"
|
||||
Tag="SunsetToSunrise" />
|
||||
</ComboBox>
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard x:Uid="LightSwitch_TurnOnDarkMode" Visibility="{x:Bind ViewModel.ScheduleMode, Mode=OneWay, Converter={StaticResource EnumToVisibilityConverter}, ConverterParameter=FixedHours}">
|
||||
<TimePicker AutomationProperties.AutomationId="DarkTimePicker" Time="{x:Bind ViewModel.DarkTimePickerValue, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard x:Uid="LightSwitch_TurnOffDarkMode" Visibility="{x:Bind ViewModel.ScheduleMode, Mode=OneWay, Converter={StaticResource EnumToVisibilityConverter}, ConverterParameter=FixedHours}">
|
||||
<TimePicker AutomationProperties.AutomationId="LightTimePicker" Time="{x:Bind ViewModel.LightTimePickerValue, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<tkcontrols:SettingsCard x:Uid="LightSwitch_LocationSettingsCard" Visibility="{x:Bind ViewModel.ScheduleMode, Mode=OneWay, Converter={StaticResource EnumToVisibilityConverter}, ConverterParameter=SunsetToSunrise}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.SyncButtonInformation, Mode=OneWay}" />
|
||||
<Button
|
||||
Padding="8"
|
||||
AutomationProperties.AutomationId="SetLocationButton_LightSwitch"
|
||||
Click="SyncLocationButton_Click"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=16}" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<tkcontrols:SettingsCard x:Uid="LightSwitch_OffsetSettingsCard" Visibility="{x:Bind ViewModel.ScheduleMode, Mode=OneWay, Converter={StaticResource EnumToVisibilityConverter}, ConverterParameter=SunsetToSunrise}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="20">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<!--<FontIcon Glyph="" FontSize="16" />-->
|
||||
<controls:IsEnabledTextBlock x:Uid="LightSwitch_SunriseText" VerticalAlignment="Center" />
|
||||
<NumberBox
|
||||
AutomationProperties.AutomationId="SunriseOffset_LightSwitch"
|
||||
Maximum="60"
|
||||
Minimum="-60"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
Value="{x:Bind ViewModel.SunriseOffset, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<controls:IsEnabledTextBlock x:Uid="LightSwitch_SunsetText" VerticalAlignment="Center" />
|
||||
<NumberBox
|
||||
AutomationProperties.AutomationId="SunsetOffset_LightSwitch"
|
||||
Maximum="60"
|
||||
Minimum="-60"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
Value="{x:Bind ViewModel.SunsetOffset, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard
|
||||
x:Name="TimelineCard"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ContentAlignment="Vertical">
|
||||
<controls:Timeline
|
||||
Margin="0,24,0,24"
|
||||
AutomationProperties.AutomationId="Timeline_LightSwitch"
|
||||
EndTime="{x:Bind ViewModel.DarkTimeTimeSpan, Mode=OneWay}"
|
||||
StartTime="{x:Bind ViewModel.LightTimeTimeSpan, Mode=OneWay}"
|
||||
Sunrise="{x:Bind ViewModel.SunriseTimeSpan, Mode=OneWay}"
|
||||
Sunset="{x:Bind ViewModel.SunsetTimeSpan, Mode=OneWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
<InfoBar
|
||||
x:Name="LocationWarningBar"
|
||||
x:Uid="LightSwitch_LocationWarningBar"
|
||||
IsOpen="True"
|
||||
Severity="Informational"
|
||||
Visibility="Collapsed" />
|
||||
<controls:SettingsGroup x:Uid="LightSwitch_BehaviorSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="LightSwitch_ApplyDarkModeExpander"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard HorizontalContentAlignment="Stretch" ContentAlignment="Left">
|
||||
<controls:CheckBoxWithDescriptionControl
|
||||
x:Uid="LightSwitch_SystemCheckbox"
|
||||
AutomationProperties.AutomationId="ChangeSystemCheckbox_LightSwitch"
|
||||
IsChecked="{x:Bind ViewModel.ChangeSystem, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard HorizontalContentAlignment="Stretch" ContentAlignment="Left">
|
||||
<controls:CheckBoxWithDescriptionControl
|
||||
x:Uid="LightSwitch_AppsCheckbox"
|
||||
AutomationProperties.AutomationId="ChangeAppsCheckbox_LightSwitch"
|
||||
IsChecked="{x:Bind ViewModel.ChangeApps, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
<!-- Force mode buttons -->
|
||||
<!--<tkcontrols:SettingsCard
|
||||
Header="Force mode now"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
Description="Apply light or dark mode immediately">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<Button
|
||||
Content="Force Light"
|
||||
Command="{x:Bind ViewModel.ForceLightCommand}" />
|
||||
<Button
|
||||
Content="Force Dark"
|
||||
Command="{x:Bind ViewModel.ForceDarkCommand}" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard>-->
|
||||
|
||||
<ContentDialog
|
||||
x:Name="LocationDialog"
|
||||
x:Uid="LightSwitch_LocationDialog"
|
||||
IsPrimaryButtonEnabled="True"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
Opened="LocationDialog_Opened"
|
||||
PrimaryButtonClick="LocationDialog_PrimaryButtonClick"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||
<Grid RowSpacing="48">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Uid="LightSwitch_LocationDialog_Description" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<!--<AutoSuggestBox
|
||||
x:Name="CityAutoSuggestBox"
|
||||
Grid.Row="1"
|
||||
Margin="0,16,0,8"
|
||||
AutomationProperties.AutomationId="CitySearchBox_LightSwitch"
|
||||
ItemsSource="{x:Bind ViewModel.SearchLocations, Mode=OneWay}"
|
||||
PlaceholderText="Search for a city near you.."
|
||||
QueryIcon="Find"
|
||||
SuggestionChosen="CityAutoSuggestBox_SuggestionChosen"
|
||||
TextChanged="CityAutoSuggestBox_TextChanged">
|
||||
<AutoSuggestBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="helpers:SearchLocation">
|
||||
<Grid Padding="12,8,0,8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="{x:Bind City}" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Country}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</AutoSuggestBox.ItemTemplate>
|
||||
</AutoSuggestBox>-->
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Margin="0,24,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Vertical"
|
||||
Spacing="32">
|
||||
<Button
|
||||
x:Name="SyncButton"
|
||||
HorizontalAlignment="Stretch"
|
||||
AutomationProperties.AutomationId="SyncLocationButton_LightSwitch"
|
||||
Style="{StaticResource AccentButtonStyle}"
|
||||
Visibility="Collapsed">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="LightSwitch_GetCurrentLocation" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<ProgressRing
|
||||
x:Name="SyncLoader"
|
||||
Grid.Row="1"
|
||||
Width="40"
|
||||
Height="40"
|
||||
VerticalAlignment="Center"
|
||||
IsActive="False"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<Grid
|
||||
x:Name="LocationResultPanel"
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Bottom"
|
||||
ColumnSpacing="16"
|
||||
RowSpacing="12"
|
||||
Visibility="Collapsed">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<FontIcon FontSize="16" Glyph="">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="LightSwitch_LocationTooltip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
AutomationProperties.AutomationId="LocationResultText_LightSwitch"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
TextAlignment="Center">
|
||||
<Run Text="{x:Bind ViewModel.Latitude, Mode=OneWay}" /><Run Text="°, " />
|
||||
<Run Text="{x:Bind ViewModel.Longitude, Mode=OneWay}" /><Run Text="°" />
|
||||
</TextBlock>
|
||||
<FontIcon
|
||||
Grid.Column="1"
|
||||
FontSize="20"
|
||||
Glyph="">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="LightSwitch_SunriseTooltip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
AutomationProperties.AutomationId="SunriseText_LightSwitch"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.LightTimeTimeSpan, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
|
||||
TextAlignment="Center" />
|
||||
<FontIcon
|
||||
Grid.Column="2"
|
||||
FontSize="20"
|
||||
Glyph="">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="LightSwitch_SunsetTooltip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
AutomationProperties.AutomationId="SunsetText_LightSwitch"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.DarkTimeTimeSpan, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
|
||||
TextAlignment="Center" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
</StackPanel>
|
||||
</controls:SettingsPageControl.ModuleContent>
|
||||
<controls:SettingsPageControl.PrimaryLinks>
|
||||
<controls:PageLink x:Uid="LearnMore_LightSwitch" Link="https://aka.ms/PowerToysOverview_LightSwitch" />
|
||||
</controls:SettingsPageControl.PrimaryLinks>
|
||||
</controls:SettingsPageControl>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="LocationEnabledStates">
|
||||
<VisualState x:Name="LocationSet" />
|
||||
<VisualState x:Name="LocationNotSet">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TimelineCard.Visibility" Value="Collapsed" />
|
||||
<Setter Target="LocationWarningBar.Visibility" Value="Visible" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Page>
|
||||
@@ -0,0 +1,324 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.PowerToys.Settings.UI.ViewModels;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using PowerToys.GPOWrapper;
|
||||
using Settings.UI.Library;
|
||||
using Settings.UI.Library.Helpers;
|
||||
using Windows.Devices.Geolocation;
|
||||
using Windows.Services.Maps;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
public sealed partial class LightSwitchPage : Page
|
||||
{
|
||||
private readonly string _appName = "LightSwitch";
|
||||
private readonly SettingsUtils _settingsUtils;
|
||||
private readonly Func<string, int> _sendConfigMsg = ShellPage.SendDefaultIPCMessage;
|
||||
|
||||
private readonly ISettingsRepository<GeneralSettings> _generalSettingsRepository;
|
||||
private readonly ISettingsRepository<LightSwitchSettings> _moduleSettingsRepository;
|
||||
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IFileSystemWatcher _fileSystemWatcher;
|
||||
private readonly DispatcherQueue _dispatcherQueue;
|
||||
|
||||
private LightSwitchViewModel ViewModel { get; set; }
|
||||
|
||||
public LightSwitchPage()
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
_sendConfigMsg = ShellPage.SendDefaultIPCMessage;
|
||||
|
||||
_generalSettingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
|
||||
_moduleSettingsRepository = SettingsRepository<LightSwitchSettings>.GetInstance(_settingsUtils);
|
||||
|
||||
// Get settings from JSON (or defaults if JSON missing)
|
||||
var darkSettings = _moduleSettingsRepository.SettingsConfig;
|
||||
|
||||
// Pass them into the ViewModel
|
||||
ViewModel = new LightSwitchViewModel(darkSettings, ShellPage.SendDefaultIPCMessage);
|
||||
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
|
||||
LoadSettings(_generalSettingsRepository, _moduleSettingsRepository);
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
var settingsPath = _settingsUtils.GetSettingsFilePath(_appName);
|
||||
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
_fileSystem = new FileSystem();
|
||||
|
||||
_fileSystemWatcher = _fileSystem.FileSystemWatcher.New();
|
||||
_fileSystemWatcher.Path = _fileSystem.Path.GetDirectoryName(settingsPath);
|
||||
_fileSystemWatcher.Filter = _fileSystem.Path.GetFileName(settingsPath);
|
||||
_fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime;
|
||||
_fileSystemWatcher.Changed += Settings_Changed;
|
||||
_fileSystemWatcher.EnableRaisingEvents = true;
|
||||
|
||||
this.InitializeComponent();
|
||||
this.Loaded += LightSwitchPage_Loaded;
|
||||
}
|
||||
|
||||
private void LightSwitchPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.SearchLocations.Count == 0)
|
||||
{
|
||||
foreach (var city in SearchLocationLoader.GetAll())
|
||||
{
|
||||
ViewModel.SearchLocations.Add(city);
|
||||
}
|
||||
}
|
||||
|
||||
ViewModel.InitializeScheduleMode();
|
||||
}
|
||||
|
||||
private async Task GetGeoLocation()
|
||||
{
|
||||
SyncButton.IsEnabled = false;
|
||||
SyncLoader.IsActive = true;
|
||||
SyncLoader.Visibility = Visibility.Visible;
|
||||
|
||||
try
|
||||
{
|
||||
// Request access
|
||||
var accessStatus = await Geolocator.RequestAccessAsync();
|
||||
if (accessStatus != GeolocationAccessStatus.Allowed)
|
||||
{
|
||||
// User denied location or it's not available
|
||||
return;
|
||||
}
|
||||
|
||||
var geolocator = new Geolocator { DesiredAccuracy = PositionAccuracy.Default };
|
||||
|
||||
Geoposition pos = await geolocator.GetGeopositionAsync();
|
||||
|
||||
double latitude = Math.Round(pos.Coordinate.Point.Position.Latitude);
|
||||
double longitude = Math.Round(pos.Coordinate.Point.Position.Longitude);
|
||||
|
||||
SunTimes result = SunCalc.CalculateSunriseSunset(
|
||||
latitude,
|
||||
longitude,
|
||||
DateTime.Now.Year,
|
||||
DateTime.Now.Month,
|
||||
DateTime.Now.Day);
|
||||
|
||||
ViewModel.LightTime = (result.SunriseHour * 60) + result.SunriseMinute;
|
||||
ViewModel.DarkTime = (result.SunsetHour * 60) + result.SunsetMinute;
|
||||
ViewModel.Latitude = latitude.ToString(CultureInfo.InvariantCulture);
|
||||
ViewModel.Longitude = longitude.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
// Since we use this mode, we can remove the selected city data.
|
||||
ViewModel.SelectedCity = null;
|
||||
|
||||
// CityAutoSuggestBox.Text = string.Empty;
|
||||
ViewModel.SyncButtonInformation = $"{ViewModel.Latitude}<7D>, {ViewModel.Longitude}<7D>";
|
||||
|
||||
// ViewModel.CityTimesText = $"Sunrise: {result.SunriseHour}:{result.SunriseMinute:D2}\n" + $"Sunset: {result.SunsetHour}:{result.SunsetMinute:D2}";
|
||||
SyncButton.IsEnabled = true;
|
||||
SyncLoader.IsActive = false;
|
||||
SyncLoader.Visibility = Visibility.Collapsed;
|
||||
LocationDialog.IsPrimaryButtonEnabled = true;
|
||||
LocationResultPanel.Visibility = Visibility.Visible;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SyncButton.IsEnabled = true;
|
||||
SyncLoader.IsActive = false;
|
||||
System.Diagnostics.Debug.WriteLine("Location error: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void LocationDialog_PrimaryButtonClick(object sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
if (ViewModel.ScheduleMode == "SunriseToSunsetUser")
|
||||
{
|
||||
ViewModel.SyncButtonInformation = ViewModel.SelectedCity.City;
|
||||
}
|
||||
else if (ViewModel.ScheduleMode == "SunriseToSunsetGeo")
|
||||
{
|
||||
ViewModel.SyncButtonInformation = $"{ViewModel.Latitude}<7D>, {ViewModel.Longitude}<7D>";
|
||||
}
|
||||
|
||||
SunriseModeChartState();
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == "IsEnabled")
|
||||
{
|
||||
if (ViewModel.IsEnabled != _generalSettingsRepository.SettingsConfig.Enabled.LightSwitch)
|
||||
{
|
||||
_generalSettingsRepository.SettingsConfig.Enabled.LightSwitch = ViewModel.IsEnabled;
|
||||
|
||||
var generalSettingsMessage = new OutGoingGeneralSettings(_generalSettingsRepository.SettingsConfig).ToString();
|
||||
Logger.LogInfo($"Saved general settings from Light Switch page.");
|
||||
|
||||
_sendConfigMsg?.Invoke(generalSettingsMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ViewModel.ModuleSettings != null)
|
||||
{
|
||||
SndLightSwitchSettings currentSettings = new(_moduleSettingsRepository.SettingsConfig);
|
||||
SndModuleSettings<SndLightSwitchSettings> csIpcMessage = new(currentSettings);
|
||||
|
||||
SndLightSwitchSettings outSettings = new(ViewModel.ModuleSettings);
|
||||
SndModuleSettings<SndLightSwitchSettings> outIpcMessage = new(outSettings);
|
||||
|
||||
string csMessage = csIpcMessage.ToJsonString();
|
||||
string outMessage = outIpcMessage.ToJsonString();
|
||||
|
||||
if (!csMessage.Equals(outMessage, StringComparison.Ordinal))
|
||||
{
|
||||
Logger.LogInfo($"Saved Light Switch settings from Light Switch page.");
|
||||
|
||||
_sendConfigMsg?.Invoke(outMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSettings(ISettingsRepository<GeneralSettings> generalSettingsRepository, ISettingsRepository<LightSwitchSettings> moduleSettingsRepository)
|
||||
{
|
||||
if (generalSettingsRepository != null)
|
||||
{
|
||||
if (moduleSettingsRepository != null)
|
||||
{
|
||||
UpdateViewModelSettings(moduleSettingsRepository.SettingsConfig, generalSettingsRepository.SettingsConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException(nameof(moduleSettingsRepository));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generalSettingsRepository));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateViewModelSettings(LightSwitchSettings lightSwitchSettings, GeneralSettings generalSettings)
|
||||
{
|
||||
if (lightSwitchSettings != null)
|
||||
{
|
||||
if (generalSettings != null)
|
||||
{
|
||||
ViewModel.IsEnabled = generalSettings.Enabled.LightSwitch;
|
||||
ViewModel.ModuleSettings = (LightSwitchSettings)lightSwitchSettings.Clone();
|
||||
|
||||
UpdateEnabledState(generalSettings.Enabled.LightSwitch);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generalSettings));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException(nameof(lightSwitchSettings));
|
||||
}
|
||||
}
|
||||
|
||||
private void Settings_Changed(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
_moduleSettingsRepository.ReloadSettings();
|
||||
LoadSettings(_generalSettingsRepository, _moduleSettingsRepository);
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateEnabledState(bool recommendedState)
|
||||
{
|
||||
var enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredLightSwitchEnabledValue();
|
||||
|
||||
if (enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
|
||||
{
|
||||
// Get the enabled state from GPO.
|
||||
ViewModel.IsEnabledGpoConfigured = true;
|
||||
ViewModel.EnabledGPOConfiguration = enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewModel.IsEnabled = recommendedState;
|
||||
}
|
||||
}
|
||||
|
||||
private async void SyncLocationButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LocationDialog.IsPrimaryButtonEnabled = false;
|
||||
LocationResultPanel.Visibility = Visibility.Collapsed;
|
||||
await LocationDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private void CityAutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput && !string.IsNullOrWhiteSpace(sender.Text))
|
||||
{
|
||||
string query = sender.Text.ToLower(CultureInfo.CurrentCulture);
|
||||
|
||||
// Filter your cities (assuming ViewModel.Cities is a List<City>)
|
||||
var filtered = ViewModel.SearchLocations
|
||||
.Where(c =>
|
||||
(c.City?.Contains(query, StringComparison.CurrentCultureIgnoreCase) ?? false) ||
|
||||
(c.Country?.Contains(query, StringComparison.CurrentCultureIgnoreCase) ?? false))
|
||||
.ToList();
|
||||
|
||||
sender.ItemsSource = filtered;
|
||||
}
|
||||
}
|
||||
|
||||
private void CityAutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
|
||||
{
|
||||
if (args.SelectedItem is SearchLocation location)
|
||||
{
|
||||
ViewModel.SelectedCity = location;
|
||||
|
||||
// CityAutoSuggestBox.Text = $"{location.City}, {location.Country}";
|
||||
LocationDialog.IsPrimaryButtonEnabled = true;
|
||||
LocationResultPanel.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private void ModeSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
SunriseModeChartState();
|
||||
}
|
||||
|
||||
private void SunriseModeChartState()
|
||||
{
|
||||
if (ViewModel.Latitude == "0.0" && ViewModel.Longitude == "0.0" && ViewModel.ScheduleMode == "SunsetToSunrise")
|
||||
{
|
||||
TimelineCard.Visibility = Visibility.Collapsed;
|
||||
LocationWarningBar.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
TimelineCard.Visibility = Visibility.Visible;
|
||||
LocationWarningBar.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private async void LocationDialog_Opened(ContentDialog sender, ContentDialogOpenedEventArgs args)
|
||||
{
|
||||
await GetGeoLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@
|
||||
Command="{x:Bind ViewModel.LaunchEventHandler}"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsClickEnabled="True" />
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
Name="RegistryPreviewDefaultRegApp"
|
||||
x:Uid="RegistryPreview_DefaultRegApp"
|
||||
|
||||
@@ -202,6 +202,12 @@
|
||||
helpers:NavHelper.NavigateTo="views:ColorPickerPage"
|
||||
AutomationProperties.AutomationId="ColorPickerNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ColorPicker.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="LightSwitchNavigationItem"
|
||||
x:Uid="Shell_LightSwitch"
|
||||
helpers:NavHelper.NavigateTo="views:LightSwitchPage"
|
||||
AutomationProperties.AutomationId="LightSwitchNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/LightSwitch.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="PowerLauncherNavigationItem"
|
||||
x:Uid="Shell_PowerLauncher"
|
||||
|
||||
Reference in New Issue
Block a user