Skip to content

Commit 6bb79cf

Browse files
committed
Implement AnalogClockFace and AnalogTimePicker drag select and improve mouse wheel interaction (on result display)
1 parent 04715b8 commit 6bb79cf

File tree

4 files changed

+506
-35
lines changed

4 files changed

+506
-35
lines changed

BionicCode.Net/BionicCode.Controls.Net.Wpf/AnalogClockFace.cs

Lines changed: 100 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,9 @@ public double SecondHandDiameter
625625
private PathGeometry SelectedSecondArcBounds { get; set; }
626626
private bool IsUpdatingSelectedTimeComponent { get; set; }
627627
private bool IsUpdatingSelectedTime { get; set; }
628+
private bool IsHourDragPickingEnabled { get; set; }
629+
private bool IsMinuteDragPickingEnabled { get; set; }
630+
private bool IsSecondsDragPickingEnabled { get; set; }
628631

629632
static AnalogClockFace()
630633
{
@@ -742,6 +745,7 @@ protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
742745
{
743746
return;
744747
}
748+
745749
Point screenPoint = e.GetPosition(this.ClockFaceCanvas);
746750
var clockAngle = GetAngleFromCartesianPoint(screenPoint);
747751
this.IsUpdatingSelectedTimeComponent = true;
@@ -750,17 +754,20 @@ protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
750754
switch (screenPoint)
751755
{
752756
case { } when this.SelectedHourArcBounds.FillContains(screenPoint):
757+
this.IsHourDragPickingEnabled = true;
753758
double steps = this.Is24HModeEnabled ? 24 : 12;
754759
degreeOfStep = 360 / steps;
755760
double hourValue = Math.Round(clockAngle / degreeOfStep, MidpointRounding.AwayFromZero) % steps;
756761
this.SelectedHour = hourValue;
757762
break;
758763
case { } when this.SelectedMinuteArcBounds.FillContains(screenPoint):
764+
this.IsMinuteDragPickingEnabled = true;
759765
degreeOfStep = 360 / 60.0;
760766
double minuteValue = Math.Round(clockAngle / degreeOfStep, MidpointRounding.AwayFromZero) % 60;
761767
this.SelectedMinute = minuteValue;
762768
break;
763769
case { } when this.SelectedSecondArcBounds.FillContains(screenPoint):
770+
this.IsSecondsDragPickingEnabled = true;
764771
degreeOfStep = 360 / 60.0;
765772
double secondValue = Math.Round(clockAngle / degreeOfStep, MidpointRounding.AwayFromZero) % 60;
766773
this.SelectedSecond = secondValue;
@@ -769,6 +776,50 @@ protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
769776
this.IsUpdatingSelectedTimeComponent = false;
770777
}
771778

779+
/// <inheritdoc />
780+
protected override void OnPreviewMouseMove(MouseEventArgs e)
781+
{
782+
base.OnPreviewMouseMove(e);
783+
if (!this.IsHourDragPickingEnabled && !IsMinuteDragPickingEnabled && !IsSecondsDragPickingEnabled)
784+
{
785+
return;
786+
}
787+
Point screenPoint = e.GetPosition(this.ClockFaceCanvas);
788+
var clockAngle = GetAngleFromCartesianPoint(screenPoint);
789+
this.IsUpdatingSelectedTimeComponent = true;
790+
double degreeOfStep;
791+
792+
switch (screenPoint)
793+
{
794+
case { } when this.IsHourDragPickingEnabled && this.SelectedHourArcBounds.FillContains(screenPoint):
795+
double steps = this.Is24HModeEnabled ? 24 : 12;
796+
degreeOfStep = 360 / steps;
797+
double hourValue = Math.Round(clockAngle / degreeOfStep, MidpointRounding.AwayFromZero) % steps;
798+
this.SelectedHour = hourValue;
799+
break;
800+
case { } when this.IsMinuteDragPickingEnabled && this.SelectedMinuteArcBounds.FillContains(screenPoint):
801+
degreeOfStep = 360 / 60.0;
802+
double minuteValue = Math.Round(clockAngle / degreeOfStep, MidpointRounding.AwayFromZero) % 60;
803+
this.SelectedMinute = minuteValue;
804+
break;
805+
case { } when this.IsSecondsDragPickingEnabled && this.SelectedSecondArcBounds.FillContains(screenPoint):
806+
degreeOfStep = 360 / 60.0;
807+
double secondValue = Math.Round(clockAngle / degreeOfStep, MidpointRounding.AwayFromZero) % 60;
808+
this.SelectedSecond = secondValue;
809+
break;
810+
}
811+
this.IsUpdatingSelectedTimeComponent = false;
812+
}
813+
814+
/// <inheritdoc />
815+
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
816+
{
817+
base.OnPreviewMouseLeftButtonUp(e);
818+
this.IsHourDragPickingEnabled = false;
819+
this.IsMinuteDragPickingEnabled = false;
820+
this.IsSecondsDragPickingEnabled = false;
821+
}
822+
772823
/// <inheritdoc />
773824
protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
774825
{
@@ -926,10 +977,18 @@ private static object CoerceHours(DependencyObject d, object basevalue)
926977
: Math.Truncate(hourValue) % 12;
927978
double decimalPart = hourValue - Math.Truncate(hourValue);
928979
var decimalMinutes = decimalPart * 60;
929-
this_.SelectedMinute = Math.Truncate(decimalMinutes);
980+
bool isOverflow = decimalMinutes >= 1.0;
981+
if (isOverflow)
982+
{
983+
this_.SelectedMinute = Math.Truncate(decimalMinutes);
984+
}
930985
decimalPart = decimalMinutes - Math.Truncate(decimalMinutes);
931-
var decimalSeconds = decimalPart * 60;
932-
this_.SelectedSecond = Math.Round(decimalSeconds, MidpointRounding.AwayFromZero);
986+
var decimalSeconds = Math.Round(decimalPart * 60, MidpointRounding.AwayFromZero);
987+
isOverflow = decimalSeconds >= 1.0;
988+
if (isOverflow)
989+
{
990+
this_.SelectedSecond = decimalSeconds;
991+
}
933992

934993
this_.IsUpdatingSelectedTimeComponent = false;
935994
return hours;
@@ -946,17 +1005,26 @@ private static object CoerceMinutes(DependencyObject d, object basevalue)
9461005
this_.IsUpdatingSelectedTimeComponent = true;
9471006
var minuteValue = (double) basevalue;
9481007
double decimalHours = minuteValue / 60;
949-
this_.SelectedHour = this_.Is24HModeEnabled
950-
? Math.Truncate(decimalHours) % 24
951-
: Math.Truncate(decimalHours) % 12 == 0
952-
? 12
953-
: Math.Truncate(decimalHours) % 12;
954-
double decimalPart = decimalHours - Math.Truncate(decimalHours);
955-
var decimalMinutes = decimalPart * 60;
1008+
bool isOverflow = decimalHours >= 1.0;
1009+
if (isOverflow)
1010+
{
1011+
this_.SelectedHour = this_.Is24HModeEnabled
1012+
? Math.Truncate(decimalHours) % 24
1013+
: Math.Truncate(decimalHours) % 12 == 0
1014+
? 12
1015+
: Math.Truncate(decimalHours) % 12;
1016+
}
1017+
1018+
var decimalMinutes = minuteValue % 60;
9561019
var minutes = Math.Truncate(decimalMinutes);
957-
decimalPart = decimalMinutes - Math.Truncate(decimalMinutes);
958-
double decimalSeconds = decimalPart * 60;
959-
this_.SelectedSecond = Math.Round(decimalSeconds, MidpointRounding.AwayFromZero);
1020+
1021+
var decimalPart = decimalMinutes - Math.Truncate(decimalMinutes);
1022+
double decimalSeconds = Math.Round(decimalPart * 60, MidpointRounding.AwayFromZero);
1023+
isOverflow = decimalSeconds >= 1.0;
1024+
if (isOverflow)
1025+
{
1026+
this_.SelectedSecond = decimalSeconds;
1027+
}
9601028

9611029
this_.IsUpdatingSelectedTimeComponent = false;
9621030
return minutes;
@@ -973,16 +1041,25 @@ private static object CoerceSeconds(DependencyObject d, object basevalue)
9731041
this_.IsUpdatingSelectedTimeComponent = true;
9741042
var secondsValue = (double) basevalue;
9751043
double decimalHours = secondsValue / 3600;
976-
this_.SelectedHour = this_.Is24HModeEnabled
977-
? Math.Truncate(decimalHours) % 24
978-
: Math.Truncate(decimalHours) % 12 == 0
979-
? 12
980-
: Math.Truncate(decimalHours) % 12;
981-
var decimalPart = decimalHours - Math.Truncate(decimalHours);
982-
double decimalMinutes = decimalPart * 60;
983-
this_.SelectedMinute = Math.Truncate(decimalMinutes);
984-
decimalPart = decimalMinutes - Math.Truncate(decimalMinutes);
985-
double decimalSeconds = decimalPart * 60;
1044+
bool isOverflow = decimalHours >= 1.0;
1045+
if (isOverflow)
1046+
{
1047+
this_.SelectedHour = this_.Is24HModeEnabled
1048+
? Math.Truncate(decimalHours) % 24
1049+
: Math.Truncate(decimalHours) % 12 == 0
1050+
? 12
1051+
: Math.Truncate(decimalHours) % 12;
1052+
}
1053+
1054+
var minutePart = secondsValue % 3600;
1055+
double decimalMinutes = minutePart / 60;
1056+
isOverflow = decimalMinutes >= 1.0;
1057+
if (isOverflow)
1058+
{
1059+
this_.SelectedMinute = Math.Truncate(decimalMinutes);
1060+
}
1061+
1062+
double decimalSeconds = minutePart % 60;
9861063
var seconds = Math.Round(decimalSeconds, MidpointRounding.AwayFromZero);
9871064

9881065
this_.IsUpdatingSelectedTimeComponent = false;

BionicCode.Net/BionicCode.Controls.Net.Wpf/AnalogTimePicker.cs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
#endregion
77

88
using System;
9+
using System.Collections.ObjectModel;
910
using System.Globalization;
11+
using System.Linq;
1012
using System.Text;
1113
using System.Windows;
1214
using System.Windows.Controls;
15+
using System.Windows.Input;
1316
using System.Windows.Markup;
17+
using BionicCode.Utilities.Net.Wpf.Extensions;
1418

1519
namespace BionicCode.Controls.Net.Wpf
1620
{
@@ -99,6 +103,42 @@ public string SelectedTimeText
99103
public bool IsClockHandVisible { get => (bool)GetValue(AnalogTimePicker.IsClockHandVisibleProperty); set => SetValue(AnalogTimePicker.IsClockHandVisibleProperty, value); }
100104

101105
#endregion IsClockHandVisible dependency property
106+
107+
#region SelectableDays dependency property
108+
109+
public static readonly DependencyProperty SelectableDaysProperty = DependencyProperty.Register(
110+
"SelectableDays",
111+
typeof(ObservableCollection<int>),
112+
typeof(AnalogTimePicker),
113+
new PropertyMetadata(default));
114+
115+
public ObservableCollection<int> SelectableDays { get => (ObservableCollection<int>) GetValue(AnalogTimePicker.SelectableDaysProperty); set => SetValue(AnalogTimePicker.SelectableDaysProperty, value); }
116+
117+
#endregion SelectableDays dependency property
118+
119+
#region IsOverflowEnabled dependency property
120+
121+
public static readonly DependencyProperty IsOverflowEnabledProperty = DependencyProperty.Register(
122+
"IsOverflowEnabled",
123+
typeof(bool),
124+
typeof(AnalogTimePicker),
125+
new PropertyMetadata(default));
126+
127+
public bool IsOverflowEnabled { get => (bool) GetValue(AnalogTimePicker.IsOverflowEnabledProperty); set => SetValue(AnalogTimePicker.IsOverflowEnabledProperty, value); }
128+
129+
#endregion IsOverflowEnabled dependency property
130+
131+
#region SelectedDays dependency property
132+
133+
public static readonly DependencyProperty SelectedDaysProperty = DependencyProperty.Register(
134+
"SelectedDays",
135+
typeof(int),
136+
typeof(AnalogTimePicker),
137+
new PropertyMetadata(default));
138+
139+
public int SelectedDays { get => (int) GetValue(AnalogTimePicker.SelectedDaysProperty); set => SetValue(AnalogTimePicker.SelectedDaysProperty, value); }
140+
141+
#endregion SelectedDays dependency property
102142
private string TimeStringFormatPattern { get; set; }
103143

104144
static AnalogTimePicker()
@@ -108,6 +148,8 @@ static AnalogTimePicker()
108148

109149
public AnalogTimePicker()
110150
{
151+
this.SelectableDays = new ObservableCollection<int>(Enumerable.Range(0, 365));
152+
this.SelectedDays = this.SelectableDays.FirstOrDefault();
111153
CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
112154
CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern = "hh.mm.ss";
113155
var timePattern = new StringBuilder();
@@ -123,9 +165,55 @@ public AnalogTimePicker()
123165
}
124166

125167
this.TimeStringFormatPattern = timePattern.ToString();
126-
//this.AnalogClockFace = new AnalogClockFace() {Background = Brushes.PaleVioletRed};
127168
}
128169

170+
#region Overrides of FrameworkElement
171+
172+
/// <inheritdoc />
173+
public override void OnApplyTemplate()
174+
{
175+
base.OnApplyTemplate();
176+
var hourPicker = GetTemplateChild("PART_HourDisplay") as UIElement;
177+
hourPicker.PreviewMouseWheel += OnMouseWheelHourSelected;
178+
var minutePicker = GetTemplateChild("PART_MinuteDisplay") as UIElement;
179+
minutePicker.PreviewMouseWheel += OnMouseWheelMinuteSelected;
180+
var secondPicker = GetTemplateChild("PART_SecondDisplay") as UIElement;
181+
secondPicker.PreviewMouseWheel += OnMouseWheelSecondSelected;
182+
}
183+
184+
private void OnMouseWheelHourSelected(object sender, MouseWheelEventArgs e)
185+
{
186+
double steps = this.AnalogClockFace.Is24HModeEnabled ? 24 : 12;
187+
int change = e.Delta > 0 ? -1 : 1;
188+
this.AnalogClockFace.SelectedHour = (steps + this.AnalogClockFace.SelectedHour + change) % steps;
189+
}
190+
191+
private void OnMouseWheelMinuteSelected(object sender, MouseWheelEventArgs e)
192+
{
193+
double steps = 60;
194+
int change = e.Delta > 0 ? -1 : 1;
195+
this.AnalogClockFace.SelectedMinute = (steps + this.AnalogClockFace.SelectedMinute + change) % steps;
196+
}
197+
198+
private void OnMouseWheelSecondSelected(object sender, MouseWheelEventArgs e)
199+
{
200+
double steps = 60;
201+
int change = e.Delta > 0 ? -1 : 1;
202+
this.AnalogClockFace.SelectedSecond = (steps + this.AnalogClockFace.SelectedSecond + change) % steps;
203+
}
204+
205+
private void OnOverflowPickerScrollChanged(object sender, ScrollChangedEventArgs e)
206+
{
207+
GenerateSelectableDays(e.VerticalChange);
208+
}
209+
210+
private void GenerateSelectableDays(double verticalChange)
211+
{
212+
;
213+
}
214+
215+
#endregion
216+
129217
private static void OnSelectedTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
130218
{
131219
var this_ = d as AnalogTimePicker;

0 commit comments

Comments
 (0)