using System; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; using System.Windows.Input; using System.Windows.Data; using System.Windows.Shapes; using System.Windows.Media.Imaging; namespace smx_config { // The checkbox to enable and disable the advanced per-panel sliders. // // This is always enabled if the thresholds in the configuration are set to different // values. If the user enables us, we'll remember that we were forced on. If the user // disables us, we'll sync the thresholds back up and turn the ForcedOn flag off. public class AdvancedThresholdViewCheckbox: CheckBox { public static readonly DependencyProperty AdvancedModeEnabledProperty = DependencyProperty.Register("AdvancedModeEnabled", typeof(bool), typeof(AdvancedThresholdViewCheckbox), new FrameworkPropertyMetadata(false)); public bool AdvancedModeEnabled { get { return (bool) GetValue(AdvancedModeEnabledProperty); } set { SetValue(AdvancedModeEnabledProperty, value); } } OnConfigChange onConfigChange; // If true, the user enabled advanced view and we should display it even if // the thresholds happen to be synced. If false, we'll only show the advanced // view if we need to because the thresholds aren't synced. bool ForcedOn; public override void OnApplyTemplate() { base.OnApplyTemplate(); onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) { LoadUIFromConfig(args.controller[args.FirstController].config); }); } private void LoadUIFromConfig(SMX.SMXConfig config) { // The master version doesn't actually matter, but we use this as a signal that the panels // have a new enough firmware to support this. bool SupportsAdvancedMode = config.masterVersion != 0xFF && config.masterVersion >= 2; Visibility = SupportsAdvancedMode? Visibility.Visible:Visibility.Collapsed; // If the thresholds are different, force the checkbox on. This way, if you load the application // with a platform with per-panel thresholds, and change the thresholds to no longer be different, // advanced mode stays forced on. It'll only turn off if you uncheck the box, or if you exit // the application with synced thresholds and then restart it. if(SupportsAdvancedMode && !ConfigPresets.AreUnifiedThresholdsSynced(config)) ForcedOn = true; // Enable advanced mode if the master says it's supported, and either the user has checked the // box to turn it on or the thresholds are different in the current configuration. AdvancedModeEnabled = SupportsAdvancedMode && ForcedOn; } protected override void OnClick() { if(AdvancedModeEnabled) { // Stop forcing advanced mode on, and sync the thresholds so we exit advanced mode. ForcedOn = false; for(int pad = 0; pad < 2; ++pad) { SMX.SMXConfig config; if(!SMX.SMX.GetConfig(pad, out config)) continue; ConfigPresets.SyncUnifiedThresholds(ref config); SMX.SMX.SetConfig(pad, config); } CurrentSMXDevice.singleton.FireConfigurationChanged(this); } else { // Enable advanced mode. ForcedOn = true; } // Refresh the UI. LoadFromConfigDelegateArgs args = CurrentSMXDevice.singleton.GetState(); LoadUIFromConfig(args.controller[args.FirstController].config); } } // This implements the threshold slider widget for changing an upper/lower threshold pair. public class ThresholdSlider: Control { public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(ThresholdSlider), new FrameworkPropertyMetadata(null)); public ImageSource Icon { get { return (ImageSource) GetValue(IconProperty); } set { SetValue(IconProperty, value); } } public static readonly DependencyProperty TypeProperty = DependencyProperty.Register("Type", typeof(string), typeof(ThresholdSlider), new FrameworkPropertyMetadata("")); public string Type { get { return (string) GetValue(TypeProperty); } set { SetValue(TypeProperty, value); } } public static readonly DependencyProperty AdvancedModeEnabledProperty = DependencyProperty.Register("AdvancedModeEnabled", typeof(bool), typeof(ThresholdSlider), new FrameworkPropertyMetadata(false, RefreshAdvancedModeEnabledCallback)); public bool AdvancedModeEnabled { get { return (bool) GetValue(AdvancedModeEnabledProperty); } set { SetValue(AdvancedModeEnabledProperty, value); } } private static void RefreshAdvancedModeEnabledCallback(DependencyObject target, DependencyPropertyChangedEventArgs args) { ThresholdSlider self = target as ThresholdSlider; self.RefreshVisibility(); } DoubleSlider slider; Label LowerLabel, UpperLabel; OnConfigChange onConfigChange; public override void OnApplyTemplate() { base.OnApplyTemplate(); slider = GetTemplateChild("Slider") as DoubleSlider; LowerLabel = GetTemplateChild("LowerValue") as Label; UpperLabel = GetTemplateChild("UpperValue") as Label; slider.ValueChanged += delegate(DoubleSlider slider) { SaveToConfig(); }; onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) { LoadUIFromConfig(args.controller[args.FirstController].config); }); } private void SetValueToConfig(ref SMX.SMXConfig config) { byte lower = (byte) slider.LowerValue; byte upper = (byte) slider.UpperValue; switch(Type) { case "up-left": config.panelThreshold0Low = lower; config.panelThreshold0High = upper; break; case "up": config.panelThreshold1Low = lower; config.panelThreshold1High = upper; break; case "up-right": config.panelThreshold2Low = lower; config.panelThreshold2High = upper; break; case "left": config.panelThreshold3Low = lower; config.panelThreshold3High = upper; break; case "center": config.panelThreshold4Low = lower; config.panelThreshold4High = upper; break; case "right": config.panelThreshold5Low = lower; config.panelThreshold5High = upper; break; case "down-left": config.panelThreshold6Low = lower; config.panelThreshold6High = upper; break; case "down": config.panelThreshold7Low = lower; config.panelThreshold7High = upper; break; case "down-right": config.panelThreshold8Low = lower; config.panelThreshold8High = upper; break; case "cardinal": config.panelThreshold7Low = lower; config.panelThreshold7High = upper; break; case "corner": config.panelThreshold2Low = lower; config.panelThreshold2High = upper; break; } // If we're not in advanced mode, sync the cardinal value to each of the panel values. if(!AdvancedModeEnabled) ConfigPresets.SyncUnifiedThresholds(ref config); } private void GetValueFromConfig(SMX.SMXConfig config, out byte lower, out byte upper) { switch(Type) { case "up-left": lower = config.panelThreshold0Low; upper = config.panelThreshold0High; return; case "up": lower = config.panelThreshold1Low; upper = config.panelThreshold1High; return; case "up-right": lower = config.panelThreshold2Low; upper = config.panelThreshold2High; return; case "left": lower = config.panelThreshold3Low; upper = config.panelThreshold3High; return; case "center": lower = config.panelThreshold4Low; upper = config.panelThreshold4High; return; case "right": lower = config.panelThreshold5Low; upper = config.panelThreshold5High; return; case "down-left": lower = config.panelThreshold6Low; upper = config.panelThreshold6High; return; case "down": lower = config.panelThreshold7Low; upper = config.panelThreshold7High; return; case "down-right": lower = config.panelThreshold8Low; upper = config.panelThreshold8High; return; case "cardinal": lower = config.panelThreshold7Low; upper = config.panelThreshold7High; return; case "corner": lower = config.panelThreshold2Low; upper = config.panelThreshold2High; return; default: lower = upper = 0; return; } } private void SaveToConfig() { if(UpdatingUI) return; // Apply the change and save it to the devices. for(int pad = 0; pad < 2; ++pad) { SMX.SMXConfig config; if(!SMX.SMX.GetConfig(pad, out config)) continue; SetValueToConfig(ref config); SMX.SMX.SetConfig(pad, config); CurrentSMXDevice.singleton.FireConfigurationChanged(this); } } bool UpdatingUI = false; private void LoadUIFromConfig(SMX.SMXConfig config) { // Make sure SaveToConfig doesn't treat these as the user changing values. UpdatingUI = true; byte lower, upper; GetValueFromConfig(config, out lower, out upper); if(lower == 0xFF) { LowerLabel.Content = "Off"; UpperLabel.Content = ""; } else { slider.LowerValue = lower; slider.UpperValue = upper; LowerLabel.Content = lower.ToString(); UpperLabel.Content = upper.ToString(); } RefreshVisibility(); UpdatingUI = false; } void RefreshVisibility() { LoadFromConfigDelegateArgs args = CurrentSMXDevice.singleton.GetState(); SMX.SMXConfig config = args.controller[args.FirstController].config; this.Visibility = ShouldBeDisplayed(config)? Visibility.Visible:Visibility.Collapsed; } // Return true if this slider should be displayed. Only display a slider if it affects // at least one panel which is enabled. private bool ShouldBeDisplayed(SMX.SMXConfig config) { bool[] enabledPanels = config.GetEnabledPanels(); // Up and center are shown in both modes. switch(Type) { case "up-left": return AdvancedModeEnabled && enabledPanels[0]; case "up": return enabledPanels[1]; case "up-right": return AdvancedModeEnabled && enabledPanels[2]; case "left": return AdvancedModeEnabled && enabledPanels[3]; case "center": return enabledPanels[4]; case "right": return AdvancedModeEnabled && enabledPanels[5]; case "down-left": return AdvancedModeEnabled && enabledPanels[6]; case "down": return AdvancedModeEnabled && enabledPanels[7]; case "down-right": return AdvancedModeEnabled && enabledPanels[8]; // Show cardinal and corner if at least one panel they affect is enabled. case "cardinal": return !AdvancedModeEnabled && (enabledPanels[3] || enabledPanels[5] || enabledPanels[8]); case "corner": return !AdvancedModeEnabled && (enabledPanels[0] || enabledPanels[2] || enabledPanels[6] || enabledPanels[8]); default: return true; } } } // A button that selects a preset, and shows a checkmark if that preset is set. public class PresetButton: Control { public static readonly DependencyProperty TypeProperty = DependencyProperty.Register("Type", typeof(string), typeof(PresetButton), new FrameworkPropertyMetadata("")); public string Type { get { return (string) GetValue(TypeProperty); } set { SetValue(TypeProperty, value); } } public static readonly DependencyProperty SelectedProperty = DependencyProperty.Register("Selected", typeof(bool), typeof(PresetButton), new FrameworkPropertyMetadata(true)); public bool Selected { get { return (bool) GetValue(SelectedProperty); } set { SetValue(SelectedProperty, value); } } public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(PresetButton), new FrameworkPropertyMetadata("")); public string Label { get { return (string) GetValue(LabelProperty); } set { SetValue(LabelProperty, value); } } private OnConfigChange onConfigChange; public override void OnApplyTemplate() { base.OnApplyTemplate(); Button button = GetTemplateChild("PART_Button") as Button; button.Click += delegate(object sender, RoutedEventArgs e) { Select(); }; onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) { string CurrentPreset = ConfigPresets.GetPreset(args.controller[args.FirstController].config); Selected = CurrentPreset == Type; }); } private void Select() { for(int pad = 0; pad < 2; ++pad) { SMX.SMXConfig config; if(!SMX.SMX.GetConfig(pad, out config)) continue; ConfigPresets.SetPreset(Type, ref config); Console.WriteLine("PresetButton::Select (" + Type + "): " + config.panelThreshold1Low + ", " + config.panelThreshold4Low + ", " + config.panelThreshold7Low + ", " + config.panelThreshold2Low); SMX.SMX.SetConfig(pad, config); } CurrentSMXDevice.singleton.FireConfigurationChanged(this); } } public class PresetWidget: Control { public static readonly DependencyProperty TypeProperty = DependencyProperty.Register("Type", typeof(string), typeof(PresetWidget), new FrameworkPropertyMetadata("")); public string Type { get { return (string) GetValue(TypeProperty); } set { SetValue(TypeProperty, value); } } public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(PresetWidget), new FrameworkPropertyMetadata("")); public string Description { get { return (string) GetValue(DescriptionProperty); } set { SetValue(DescriptionProperty, value); } } public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(PresetWidget), new FrameworkPropertyMetadata("")); public string Label { get { return (string) GetValue(LabelProperty); } set { SetValue(LabelProperty, value); } } } public class PanelButton: ToggleButton { public static readonly DependencyProperty ButtonProperty = DependencyProperty.RegisterAttached("Button", typeof(string), typeof(PanelButton), new FrameworkPropertyMetadata(null)); public string Button { get { return (string) this.GetValue(ButtonProperty); } set { this.SetValue(ButtonProperty, value); } } protected override void OnIsPressedChanged(DependencyPropertyChangedEventArgs e) { base.OnIsPressedChanged(e); } } // A base class for buttons used to select a panel to work with. public class PanelSelectButton: Button { // Which panel this is (P1 0-8, P2 9-17): public static readonly DependencyProperty PanelProperty = DependencyProperty.RegisterAttached("Panel", typeof(int), typeof(PanelSelectButton), new FrameworkPropertyMetadata(0, RefreshIsSelectedCallback)); public int Panel { get { return (int) this.GetValue(PanelProperty); } set { this.SetValue(PanelProperty, value); } } // Which panel is currently selected. If this == Panel, this panel is selected. This is // bound to ColorPicker.SelectedPanel, so changing this changes which panel the picker edits. public static readonly DependencyProperty SelectedPanelProperty = DependencyProperty.RegisterAttached("SelectedPanel", typeof(int), typeof(PanelSelectButton), new FrameworkPropertyMetadata(0, RefreshIsSelectedCallback)); public int SelectedPanel { get { return (int) this.GetValue(SelectedPanelProperty); } set { this.SetValue(SelectedPanelProperty, value); } } // Whether this panel is selected. This is true if Panel == SelectedPanel. public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.RegisterAttached("IsSelected", typeof(bool), typeof(PanelSelectButton), new FrameworkPropertyMetadata(false)); public bool IsSelected { get { return (bool) this.GetValue(IsSelectedProperty); } set { this.SetValue(IsSelectedProperty, value); } } // When Panel or SelectedPanel change, update IsSelected. private static void RefreshIsSelectedCallback(DependencyObject target, DependencyPropertyChangedEventArgs args) { PanelSelectButton self = target as PanelSelectButton; self.RefreshIsSelected(); } private void RefreshIsSelected() { IsSelected = Panel == SelectedPanel; } public override void OnApplyTemplate() { base.OnApplyTemplate(); RefreshIsSelected(); } } // This button shows the color configured for that panel, and chooses which color is being // edited by the ColorPicker. public class PanelColorButton: PanelSelectButton { // The color configured for this panel: public static readonly DependencyProperty PanelColorProperty = DependencyProperty.RegisterAttached("PanelColor", typeof(SolidColorBrush), typeof(PanelColorButton), new FrameworkPropertyMetadata(new SolidColorBrush())); public SolidColorBrush PanelColor { get { return (SolidColorBrush) this.GetValue(PanelColorProperty); } set { this.SetValue(PanelColorProperty, value); } } public override void OnApplyTemplate() { base.OnApplyTemplate(); OnConfigChange onConfigChange; onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) { int pad = Panel < 9? 0:1; LoadUIFromConfig(args.controller[pad].config); }); } protected override void OnClick() { base.OnClick(); // Select this panel. SelectedPanel = Panel; // Fire configuration changed, so the color slider updates to show this panel. CurrentSMXDevice.singleton.FireConfigurationChanged(this); } // Set PanelColor. This widget doesn't change the color, it only reflects the current configuration. private void LoadUIFromConfig(SMX.SMXConfig config) { int PanelIndex = Panel % 9; // Hide color buttons for disabled panels. bool[] enabledPanels = config.GetEnabledPanels(); Visibility = enabledPanels[PanelIndex]? Visibility.Visible:Visibility.Hidden; Color rgb = ColorPicker.UnscaleColor(Color.FromRgb( config.stepColor[PanelIndex*3+0], config.stepColor[PanelIndex*3+1], config.stepColor[PanelIndex*3+2])); PanelColor = new SolidColorBrush(rgb); } // Return #RRGGBB for the color set on this panel. private string GetColorString() { // WPF's Color.ToString() returns #AARRGGBB, which is just wrong. Alpha is always // last in HTML color codes. We don't need alpha, so just strip it off. return "#" + PanelColor.Color.ToString().Substring(3); } // Parse #RRGGBB and return a Color, or white if the string isn't in the correct format. private static Color ParseColorString(string s) { // We only expect "#RRGGBB". if(s.Length != 7 || !s.StartsWith("#")) return Color.FromRgb(255,255,255); try { return (Color) ColorConverter.ConvertFromString(s); } catch(System.FormatException) { return Color.FromRgb(255,255,255); } } Point MouseDownPosition; protected override void OnMouseDown(MouseButtonEventArgs e) { MouseDownPosition = e.GetPosition(null); base.OnMouseDown(e); } // Handle initiating drag. protected override void OnMouseMove(MouseEventArgs e) { if(e.LeftButton == MouseButtonState.Pressed) { Point position = e.GetPosition(null); // Why do we have to handle drag thresholding manually? This is the platform's job. // If we don't do this, clicks won't work at all. if (Math.Abs(position.X - MouseDownPosition.X) >= SystemParameters.MinimumHorizontalDragDistance || Math.Abs(position.Y - MouseDownPosition.Y) >= SystemParameters.MinimumVerticalDragDistance) { DragDrop.DoDragDrop(this, GetColorString(), DragDropEffects.Copy); } } base.OnMouseMove(e); } private bool HandleDrop(DragEventArgs e) { PanelColorButton Button = e.Source as PanelColorButton; if(Button == null) return false; // A color is being dropped from another button. Don't just update our color, since // that will just change the button color and not actually apply it. DataObject data = e.Data as DataObject; if(data == null) return false; // Parse the color being dragged onto us. Color color = ParseColorString(data.GetData(typeof(string)) as string); // Update the panel color. int PanelIndex = Panel % 9; int Pad = Panel < 9? 0:1; SMX.SMXConfig config; if(!SMX.SMX.GetConfig(Pad, out config)) return false; // Light colors are 8-bit values, but we only use values between 0-170. Higher values // don't make the panel noticeably brighter, and just draw more power. config.stepColor[PanelIndex*3+0] = ColorPicker.ScaleColor(color.R); config.stepColor[PanelIndex*3+1] = ColorPicker.ScaleColor(color.G); config.stepColor[PanelIndex*3+2] = ColorPicker.ScaleColor(color.B); SMX.SMX.SetConfig(Pad, config); CurrentSMXDevice.singleton.FireConfigurationChanged(this); return true; } protected override void OnDrop(DragEventArgs e) { if(!HandleDrop(e)) base.OnDrop(e); } } // This is a Slider class with some added helpers. public class Slider2: Slider { public delegate void DragEvent(); public event DragEvent StartedDragging, StoppedDragging; protected Thumb Thumb; public override void OnApplyTemplate() { base.OnApplyTemplate(); Track track = Template.FindName("PART_Track", this) as Track; Thumb = track.Thumb; } // How are there no events for this? protected override void OnThumbDragStarted(DragStartedEventArgs e) { base.OnThumbDragStarted(e); StartedDragging?.Invoke(); } protected override void OnThumbDragCompleted(DragCompletedEventArgs e) { base.OnThumbDragCompleted(e); StoppedDragging?.Invoke(); } public Slider2() { // Fix the slider not dragging after clicking outside the thumb. // http://stackoverflow.com/a/30575638/136829 bool clickedInSlider = false; MouseMove += delegate(object sender, MouseEventArgs args) { if(args.LeftButton == MouseButtonState.Released || !clickedInSlider || Thumb.IsDragging) return; Thumb.RaiseEvent(new MouseButtonEventArgs(args.MouseDevice, args.Timestamp, MouseButton.Left) { RoutedEvent = UIElement.MouseLeftButtonDownEvent, Source = args.Source, }); }; AddHandler(UIElement.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler((sender, args) => { clickedInSlider = true; }), true); AddHandler(UIElement.PreviewMouseLeftButtonUpEvent, new RoutedEventHandler((sender, args) => { clickedInSlider = false; }), true); } }; // This is the Slider inside a ColorPicker. public class ColorPickerSlider: Slider2 { public ColorPickerSlider() { } }; public class ColorPicker: Control { // Which panel is currently selected: public static readonly DependencyProperty SelectedPanelProperty = DependencyProperty.Register("SelectedPanel", typeof(int), typeof(ColorPicker), new FrameworkPropertyMetadata(0)); public int SelectedPanel { get { return (int) this.GetValue(SelectedPanelProperty); } set { this.SetValue(SelectedPanelProperty, value); } } ColorPickerSlider HueSlider; public delegate void Event(); public event Event StartedDragging, StoppedDragging; public override void OnApplyTemplate() { base.OnApplyTemplate(); HueSlider = GetTemplateChild("HueSlider") as ColorPickerSlider; HueSlider.ValueChanged += delegate(object sender, RoutedPropertyChangedEventArgs e) { SaveToConfig(); }; HueSlider.StartedDragging += delegate() { StartedDragging?.Invoke(); }; HueSlider.StoppedDragging += delegate() { StoppedDragging?.Invoke(); }; DoubleCollection ticks = new DoubleCollection(); // Add a tick at the minimum value, which is a negative value. This is the // tick for white. ticks.Add(HueSlider.Minimum); // Add a tick for 0-359. Don't add 360, since that's the same as 0. for(int i = 0; i < 360; ++i) ticks.Add(i); HueSlider.Ticks = ticks; OnConfigChange onConfigChange; onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) { int pad = SelectedPanel < 9? 0:1; LoadUIFromConfig(args.controller[pad].config); }); } // Light values are actually in the range 0-170 and not 0-255, since higher values aren't // any brighter and just draw more power. The auto-lighting colors that we're configuring // need to be scaled to this range too, but show full range colors in the UI. readonly static double LightsScaleFactor = 0.666666f; static public Byte ScaleColor(Byte c) { return (Byte) (c * LightsScaleFactor); } static public Byte UnscaleColor(Byte c) { return (Byte) Math.Min(255, c / LightsScaleFactor); } static public Color ScaleColor(Color c) { return Color.FromRgb( ColorPicker.ScaleColor(c.R), ColorPicker.ScaleColor(c.G), ColorPicker.ScaleColor(c.B)); } static public Color UnscaleColor(Color c) { return Color.FromRgb( ColorPicker.UnscaleColor(c.R), ColorPicker.UnscaleColor(c.G), ColorPicker.UnscaleColor(c.B)); } private void SaveToConfig() { if(UpdatingUI) return; // Apply the change and save it to the device. int pad = SelectedPanel < 9? 0:1; SMX.SMXConfig config; if(!SMX.SMX.GetConfig(pad, out config)) return; Color color = Helpers.FromHSV(HueSlider.Value, 1, 1); // If we're set to the minimum value, use white instead. if(HueSlider.Value == HueSlider.Minimum) color = Color.FromRgb(255,255,255); // Light colors are 8-bit values, but we only use values between 0-170. Higher values // don't make the panel noticeably brighter, and just draw more power. int PanelIndex = SelectedPanel % 9; config.stepColor[PanelIndex*3+0] = ScaleColor(color.R); config.stepColor[PanelIndex*3+1] = ScaleColor(color.G); config.stepColor[PanelIndex*3+2] = ScaleColor(color.B); SMX.SMX.SetConfig(pad, config); CurrentSMXDevice.singleton.FireConfigurationChanged(this); } bool UpdatingUI = false; private void LoadUIFromConfig(SMX.SMXConfig config) { // Make sure SaveToConfig doesn't treat these as the user changing values. UpdatingUI = true; // Reverse the scaling we applied in SaveToConfig. int PanelIndex = SelectedPanel % 9; Color rgb = Color.FromRgb( UnscaleColor(config.stepColor[PanelIndex*3+0]), UnscaleColor(config.stepColor[PanelIndex*3+1]), UnscaleColor(config.stepColor[PanelIndex*3+2])); double h, s, v; Helpers.ToHSV(rgb, out h, out s, out v); // Check for white. Since the conversion through LightsScaleFactor may not round trip // back to exactly #FFFFFF, give some room for error in the value (brightness). if(s <= 0.001 && v >= .90) { // This is white, so set it to the white block at the left edge of the slider. HueSlider.Value = HueSlider.Minimum; } else { HueSlider.Value = h; } UpdatingUI = false; } }; // This widget selects which panels are enabled. We only show one of these for both pads. class PanelSelector: Control { PanelButton[] EnabledPanelButtons; OnConfigChange onConfigChange; public override void OnApplyTemplate() { base.OnApplyTemplate(); int[] PanelToIndex = new int[] { 7, 8, 9, 4, 5, 6, 1, 2, 3, }; EnabledPanelButtons = new PanelButton[9]; for(int i = 0; i < 9; ++i) EnabledPanelButtons[i] = GetTemplateChild("EnablePanel" + PanelToIndex[i]) as PanelButton; foreach(PanelButton button in EnabledPanelButtons) button.Click += EnabledPanelButtonClicked; onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) { LoadUIFromConfig(args.controller[args.FirstController].config); }); } private void LoadUIFromConfig(SMX.SMXConfig config) { // The firmware configuration allows disabling each of the four sensors in a panel // individually, but currently we only have a UI for toggling the whole sensor. Taking // individual sensors out isn't recommended. bool[] enabledPanels = { (config.enabledSensors[0] & 0xF0) != 0, (config.enabledSensors[0] & 0x0F) != 0, (config.enabledSensors[1] & 0xF0) != 0, (config.enabledSensors[1] & 0x0F) != 0, (config.enabledSensors[2] & 0xF0) != 0, (config.enabledSensors[2] & 0x0F) != 0, (config.enabledSensors[3] & 0xF0) != 0, (config.enabledSensors[3] & 0x0F) != 0, (config.enabledSensors[4] & 0xF0) != 0, }; for(int i = 0; i < 9; ++i) EnabledPanelButtons[i].IsChecked = enabledPanels[i]; } private int GetIndexFromButton(object sender) { for(int i = 0; i < 9; i++) { if(sender == EnabledPanelButtons[i]) return i; } return 0; } private void EnabledPanelButtonClicked(object sender, EventArgs e) { // One of the panel buttons on the panel toggle UI was clicked. Toggle the // panel. int button = GetIndexFromButton(sender); Console.WriteLine("Clicked " + button); // Set the enabled sensor mask on both pads to the state of the UI. for(int pad = 0; pad < 2; ++pad) { SMX.SMXConfig config; if(!SMX.SMX.GetConfig(pad, out config)) continue; // This could be done algorithmically, but this is clearer. int[] PanelButtonToSensorIndex = { 0, 0, 1, 1, 2, 2, 3, 3, 4 }; byte[] PanelButtonToSensorMask = { 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, }; for(int i = 0; i < 5; ++i) config.enabledSensors[i] = 0; for(int Panel = 0; Panel < 9; ++Panel) { int index = PanelButtonToSensorIndex[Panel]; byte mask = PanelButtonToSensorMask[Panel]; if(EnabledPanelButtons[Panel].IsChecked == true) config.enabledSensors[index] |= (byte) mask; } SMX.SMX.SetConfig(pad, config); } } }; public class FrameImage: Image { // The source image. Changing this after load isn't supported. public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(BitmapSource), typeof(FrameImage), new FrameworkPropertyMetadata(null, ImageChangedCallback)); public BitmapSource Image { get { return (BitmapSource) this.GetValue(ImageProperty); } set { this.SetValue(ImageProperty, value); } } // Which frame is currently displayed: public static readonly DependencyProperty FrameProperty = DependencyProperty.Register("Frame", typeof(int), typeof(FrameImage), new FrameworkPropertyMetadata(0, FrameChangedCallback)); public int Frame { get { return (int) this.GetValue(FrameProperty); } set { this.SetValue(FrameProperty, value); } } public static readonly DependencyProperty FramesXProperty = DependencyProperty.Register("FramesX", typeof(int), typeof(FrameImage), new FrameworkPropertyMetadata(0, ImageChangedCallback)); public int FramesX { get { return (int) this.GetValue(FramesXProperty); } set { this.SetValue(FramesXProperty, value); } } private static void ImageChangedCallback(DependencyObject target, DependencyPropertyChangedEventArgs args) { FrameImage self = target as FrameImage; self.Load(); } private static void FrameChangedCallback(DependencyObject target, DependencyPropertyChangedEventArgs args) { FrameImage self = target as FrameImage; self.Refresh(); } private BitmapSource[] ImageFrames; private void Load() { if(Image == null || FramesX == 0) { ImageFrames = null; return; } // Split the image into frames. int FrameWidth = Image.PixelWidth / FramesX; int FrameHeight = Image.PixelHeight; ImageFrames = new BitmapSource[FramesX]; for(int i = 0; i < FramesX; ++i) ImageFrames[i] = new CroppedBitmap(Image, new Int32Rect(FrameWidth*i, 0, FrameWidth, FrameHeight)); Refresh(); } private void Refresh() { if(ImageFrames == null || Frame >= ImageFrames.Length) { this.Source = null; return; } this.Source = ImageFrames[Frame]; } }; }