]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
SwitchButton (#39161)
authorAbsotively <jen@jenpollock.ca>
Thu, 22 Jan 2026 17:31:56 +0000 (10:31 -0700)
committerGitHub <noreply@github.com>
Thu, 22 Jan 2026 17:31:56 +0000 (17:31 +0000)
* Initial toggle switch styling

* tweak toggle switch textures

* Simplify toggle SVG images a bit

* Better name for switch button

* Update CheckButtons that were already just regular buttons

* Match checkbox/text field outline colour instead of slider outline colour

* Use switch button for APC power

* Update switch button styling; add separate style for power buttons

* Use new switch button in midi channels menu

* Add spacer

* adjust switch button icon proportions, position

* Add disabled toggle switch styles, use improved pressed style setup, make APC breaker state visible to all

* Use Janet Blackquill's icon design; remove StyleClassPowerSwitchButton. Co-authored-by: Janet Blackquill <uhhadd@gmail.com>

* Style switch children directly instead of with propagated styles

* Add attributions file

* Turns out source is a required field

* Move SwitchButton out of engine

* Move styles to sheetlet

* Make workaround for child controls not updating work in content

* Icon layers

* Set up ISwitchButtonConfig

* Fix disabled switch label font color

* Don't redefine base pseudostyles

* Use pseudoclass helpers for better readability

* Use margin instead of padding element

* Remove unused using statements

* Remove extra image file

* Update attributions for changed files

23 files changed:
Content.Client/Instruments/UI/ChannelsMenu.xaml
Content.Client/Instruments/UI/ChannelsMenu.xaml.cs
Content.Client/Power/APC/UI/ApcMenu.xaml
Content.Client/Power/APC/UI/ApcMenu.xaml.cs
Content.Client/Stylesheets/CommonStylesheet.cs
Content.Client/Stylesheets/SheetletConfigs/ISwitchButtonConfig.cs [new file with mode: 0644]
Content.Client/Stylesheets/Sheetlets/SwitchButtonSheetlet.cs [new file with mode: 0644]
Content.Client/UserInterface/Controls/SwitchButton.cs [new file with mode: 0644]
Resources/Locale/en-US/ui/controls.ftl
Resources/Locale/en-US/ui/power-apc.ftl
Resources/Textures/Interface/Nano/attributions.yml [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_symbol_off.svg [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_symbol_off.svg.96dpi.png [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_symbol_on.svg [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_symbol_on.svg.96dpi.png [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_thumb_fill.svg [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_thumb_fill.svg.96dpi.png [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_thumb_outline.svg [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_thumb_outline.svg.96dpi.png [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_track_fill.svg [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_track_fill.svg.96dpi.png [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_track_outline.svg [new file with mode: 0644]
Resources/Textures/Interface/Nano/switchbutton_track_outline.svg.96dpi.png [new file with mode: 0644]

index e98b03e6b324539857b9e1bf2eadf0798a1b5ce5..f902cad4d0f3ac23105874f4b5b254215c115bb4 100644 (file)
@@ -1,5 +1,5 @@
 <DefaultWindow Title="{Loc 'instruments-component-channels-menu'}" MinSize="250 350" xmlns="https://spacestation14.io"
-               xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
+               xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
     <BoxContainer Orientation="Vertical" HorizontalExpand="true" VerticalExpand="true" Align="Center">
         <ItemList Name="ChannelList" SelectMode="Multiple" Margin="3 3 3 3" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="8"/>
         <BoxContainer Orientation="Horizontal" HorizontalExpand="true" VerticalExpand="true" Align="Center"
@@ -7,7 +7,8 @@
             <Button Name="AllButton" Text="{Loc 'instruments-component-channels-all-button'}" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="1"/>
             <Button Name="ClearButton" Text="{Loc 'instruments-component-channels-clear-button'}" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="1"/>
         </BoxContainer>
-        <Button Name="DisplayTrackNames"
-                     Text="{Loc 'instruments-component-channels-track-names-toggle'}" />
+        <controls:SwitchButton Name="DisplayTrackNames"
+                Text="{Loc 'instruments-component-channels-track-names-toggle'}"
+                Margin="0 5 0 0" />
     </BoxContainer>
 </DefaultWindow>
index da164a633cde0d8593e35de4283b1f20ad46fd9a..0f51c76f8fc00092067725d54d85de01ad55b5d7 100644 (file)
@@ -37,7 +37,6 @@ public sealed partial class ChannelsMenu : DefaultWindow
 
     private void OnDisplayTrackNamesPressed(BaseButton.ButtonEventArgs obj)
     {
-        DisplayTrackNames.SetClickPressed(!DisplayTrackNames.Pressed);
         Populate();
     }
 
index 7dd38c76fcb06dc0258a53b48fd793c3eb18675e..24b1371f3613ba0974f210b8f12e59021167ecdd 100644 (file)
@@ -1,7 +1,6 @@
 <controls:FancyWindow  xmlns="https://spacestation14.io"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
-    xmlns:style="clr-namespace:Content.Client.Stylesheets"
     Name="APCMenu"
     Title="{Loc 'apc-menu-title'}"
     Resizable="False">
@@ -19,9 +18,7 @@
                         <!-- Power On/Off -->
                         <Label Text="{Loc 'apc-menu-breaker-label'}" HorizontalExpand="True"
                                 StyleClasses="highlight" MinWidth="120"/>
-                        <BoxContainer Orientation="Horizontal" MinWidth="150">
-                            <Button Name="BreakerButton" Text="{Loc 'apc-menu-breaker-button'}" HorizontalExpand="True" ToggleMode="True"/>
-                        </BoxContainer>
+                        <controls:SwitchButton Name="BreakerButton" MinWidth="150"/>
                         <!--Charging Status-->
                         <Label Text="{Loc 'apc-menu-external-label'}" StyleClasses="highlight" MinWidth="120" />
                         <Label Name="ExternalPowerStateLabel" Text="{Loc 'apc-menu-power-state-good'}" />
index 097ee715819e263db504281d5fb088ebac217b7e..df45853bfb88027949b1cd701fdb9007239f0b34 100644 (file)
@@ -33,10 +33,7 @@ namespace Content.Client.Power.APC.UI
         {
             var castState = (ApcBoundInterfaceState) state;
 
-            if (!BreakerButton.Disabled)
-            {
-                BreakerButton.Pressed = castState.MainBreaker;
-            }
+            BreakerButton.Pressed = castState.MainBreaker;
 
             if (PowerLabel != null)
             {
index f8eae88b38e8225f5b1b6a17245bfa236c9d4923..72b58e56483b78750061f60b9f86b01b3655b851 100644 (file)
@@ -6,7 +6,7 @@ namespace Content.Client.Stylesheets;
 
 public abstract class CommonStylesheet : PalettedStylesheet, IButtonConfig, IWindowConfig, IIconConfig, ITabContainerConfig,
     ISliderConfig, IRadialMenuConfig, IPlaceholderConfig, ITooltipConfig, IPanelConfig, INanoHeadingConfig,
-    ILineEditConfig, IStripebackConfig, ICheckboxConfig
+    ILineEditConfig, IStripebackConfig, ICheckboxConfig, ISwitchButtonConfig
 {
     /// <remarks>
     ///     This constructor will not access any virtual or abstract properties, so you can set them from your config.
@@ -73,4 +73,11 @@ public abstract class CommonStylesheet : PalettedStylesheet, IButtonConfig, IWin
     ColorPalette IButtonConfig.ButtonPalette => PrimaryPalette with { PressedElement = PositivePalette.PressedElement };
     ColorPalette IButtonConfig.PositiveButtonPalette => PositivePalette;
     ColorPalette IButtonConfig.NegativeButtonPalette => NegativePalette;
+
+    ResPath ISwitchButtonConfig.SwitchButtonTrackFillPath => new("switchbutton_track_fill.svg.96dpi.png");
+    ResPath ISwitchButtonConfig.SwitchButtonTrackOutlinePath => new("switchbutton_track_outline.svg.96dpi.png");
+    ResPath ISwitchButtonConfig.SwitchButtonThumbFillPath => new("switchbutton_thumb_fill.svg.96dpi.png");
+    ResPath ISwitchButtonConfig.SwitchButtonThumbOutlinePath => new("switchbutton_thumb_outline.svg.96dpi.png");
+    ResPath ISwitchButtonConfig.SwitchButtonSymbolOffPath => new("switchbutton_symbol_off.svg.96dpi.png");
+    ResPath ISwitchButtonConfig.SwitchButtonSymbolOnPath => new("switchbutton_symbol_on.svg.96dpi.png");
 }
diff --git a/Content.Client/Stylesheets/SheetletConfigs/ISwitchButtonConfig.cs b/Content.Client/Stylesheets/SheetletConfigs/ISwitchButtonConfig.cs
new file mode 100644 (file)
index 0000000..0697bc9
--- /dev/null
@@ -0,0 +1,14 @@
+using Robust.Shared.Utility;
+
+namespace Content.Client.Stylesheets.SheetletConfigs;
+
+public interface ISwitchButtonConfig
+{
+    public ResPath SwitchButtonTrackFillPath { get; }
+    public ResPath SwitchButtonTrackOutlinePath { get; }
+    public ResPath SwitchButtonThumbFillPath { get; }
+    public ResPath SwitchButtonThumbOutlinePath { get; }
+    public ResPath SwitchButtonSymbolOffPath { get; }
+    public ResPath SwitchButtonSymbolOnPath { get; }
+}
+
diff --git a/Content.Client/Stylesheets/Sheetlets/SwitchButtonSheetlet.cs b/Content.Client/Stylesheets/Sheetlets/SwitchButtonSheetlet.cs
new file mode 100644 (file)
index 0000000..fea9241
--- /dev/null
@@ -0,0 +1,125 @@
+using Content.Client.Stylesheets.SheetletConfigs;
+using Content.Client.Stylesheets.Stylesheets;
+using Content.Client.UserInterface.Controls;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using static Content.Client.Stylesheets.StylesheetHelpers;
+
+namespace Content.Client.Stylesheets.Sheetlets;
+
+[CommonSheetlet]
+public sealed class SwitchButtonSheetlet<T> : Sheetlet<T> where T : PalettedStylesheet, ISwitchButtonConfig
+{
+    public override StyleRule[] GetRules(T sheet, object config)
+    {
+        ISwitchButtonConfig switchButtonCfg = sheet;
+
+        var trackFillTex = sheet.GetTextureOr(switchButtonCfg.SwitchButtonTrackFillPath, NanotrasenStylesheet.TextureRoot);
+        var trackOutlineTex = sheet.GetTextureOr(switchButtonCfg.SwitchButtonTrackOutlinePath, NanotrasenStylesheet.TextureRoot);
+        var thumbFillTex = sheet.GetTextureOr(switchButtonCfg.SwitchButtonThumbFillPath, NanotrasenStylesheet.TextureRoot);
+        var thumbOutlineTex = sheet.GetTextureOr(switchButtonCfg.SwitchButtonThumbOutlinePath, NanotrasenStylesheet.TextureRoot);
+        var symbolOffTex = sheet.GetTextureOr(switchButtonCfg.SwitchButtonSymbolOffPath, NanotrasenStylesheet.TextureRoot);
+        var symbolOnTex = sheet.GetTextureOr(switchButtonCfg.SwitchButtonSymbolOnPath, NanotrasenStylesheet.TextureRoot);
+
+        return
+        [
+            // SwitchButton
+            E<SwitchButton>().Prop(SwitchButton.StylePropertySeparation, 10),
+
+            E<SwitchButton>()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassTrackFill))
+                .Prop(TextureRect.StylePropertyTexture, trackFillTex)
+                .Modulate(sheet.SecondaryPalette.BackgroundDark),
+
+            E<SwitchButton>()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassTrackOutline))
+                .Prop(TextureRect.StylePropertyTexture, trackOutlineTex)
+                .Modulate(sheet.SecondaryPalette.Text),
+
+            E<SwitchButton>()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassThumbFill))
+                .Prop(TextureRect.StylePropertyTexture, thumbFillTex)
+                .Modulate(sheet.PrimaryPalette.Element)
+                .HorizontalAlignment(Control.HAlignment.Left),
+
+            E<SwitchButton>()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassThumbOutline))
+                .Prop(TextureRect.StylePropertyTexture, thumbOutlineTex)
+                .Modulate(sheet.PrimaryPalette.Text)
+                .HorizontalAlignment(Control.HAlignment.Left),
+
+            E<SwitchButton>()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassSymbol))
+                .Prop(TextureRect.StylePropertyTexture, symbolOffTex)
+                .Modulate(sheet.SecondaryPalette.Text),
+
+            // Pressed styles
+            E<SwitchButton>()
+                .PseudoPressed()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassTrackFill))
+                .Modulate(sheet.PositivePalette.Text),
+
+            E<SwitchButton>()
+                .PseudoPressed()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassSymbol))
+                .Prop(TextureRect.StylePropertyTexture, symbolOnTex)
+                .Modulate(Color.White), // Same color as text, not yet in any of the palettes
+
+            E<SwitchButton>()
+                .PseudoPressed()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassThumbFill))
+                .HorizontalAlignment(Control.HAlignment.Right),
+
+            E<SwitchButton>()
+                .PseudoPressed()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassThumbOutline))
+                .HorizontalAlignment(Control.HAlignment.Right),
+
+            // Disabled styles
+            E<SwitchButton>()
+                .PseudoDisabled()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassTrackFill))
+                .Modulate(sheet.SecondaryPalette.DisabledElement),
+
+            E<SwitchButton>()
+                .PseudoDisabled()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassTrackOutline))
+                .Modulate(sheet.SecondaryPalette.DisabledElement),
+
+            E<SwitchButton>()
+                .PseudoDisabled()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassThumbFill))
+                .Modulate(sheet.PrimaryPalette.DisabledElement),
+
+            E<SwitchButton>()
+                .PseudoDisabled()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassThumbOutline))
+                .Modulate(sheet.PrimaryPalette.TextDark),
+
+            E<SwitchButton>()
+                .PseudoDisabled()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassSymbol))
+                .Modulate(sheet.SecondaryPalette.TextDark),
+
+            E<SwitchButton>()
+                .PseudoDisabled()
+                .ParentOf(E<Label>())
+                .Modulate(sheet.PrimaryPalette.TextDark),
+
+            // Both pressed & disabled styles
+            // Note that some of the pressed-only and disabled-only styles do not conflict
+            // and will also be used
+            E<SwitchButton>()
+                .PseudoPressed()
+                .PseudoDisabled()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassTrackFill))
+                .Modulate(sheet.PositivePalette.DisabledElement),
+
+            E<SwitchButton>()
+                .PseudoPressed()
+                .PseudoDisabled()
+                .ParentOf(E<TextureRect>().Class(SwitchButton.StyleClassSymbol))
+                .Modulate(sheet.PositivePalette.Text),
+        ];
+    }
+}
diff --git a/Content.Client/UserInterface/Controls/SwitchButton.cs b/Content.Client/UserInterface/Controls/SwitchButton.cs
new file mode 100644 (file)
index 0000000..cf7af5b
--- /dev/null
@@ -0,0 +1,317 @@
+using System.Numerics;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.UserInterface.Controls
+{
+    /// <summary>
+    ///     A type of toggleable button that a switch icon and a secondary text label both showing the current state
+    /// </summary>
+    [Virtual]
+    public class SwitchButton : ContainerButton
+    {
+        public const string StyleClassTrackFill = "trackFill";
+        public const string StyleClassTrackOutline = "trackOutline";
+        public const string StyleClassThumbFill = "thumbFill";
+        public const string StyleClassThumbOutline = "thumbOutline";
+        public const string StyleClassSymbol = "symbol";
+
+        public const string StylePropertySeparation = "separation";
+
+        private const int DefaultSeparation = 0;
+
+        private int ActualSeparation
+        {
+            get
+            {
+                if (TryGetStyleProperty(StylePropertySeparation, out int separation))
+                {
+                    return separation;
+                }
+
+                return SeparationOverride ?? DefaultSeparation;
+            }
+        }
+
+        public int? SeparationOverride { get; set; }
+        public Label Label { get; }
+        public Label OffStateLabel { get; }
+        public Label OnStateLabel { get; }
+
+        // I tried to find a way not to have five textures here, but the other
+        // options were worse.
+        public TextureRect TrackFill { get; }
+        public TextureRect TrackOutline { get; }
+        public TextureRect ThumbFill { get; }
+        public TextureRect ThumbOutline { get; }
+        public TextureRect Symbol { get; }
+
+        public SwitchButton()
+        {
+            ToggleMode = true;
+
+            TrackFill = new TextureRect
+            {
+                StyleClasses = { StyleClassTrackFill },
+                VerticalAlignment = VAlignment.Center,
+            };
+
+            TrackOutline = new TextureRect
+            {
+                StyleClasses = { StyleClassTrackOutline },
+                VerticalAlignment = VAlignment.Center,
+            };
+
+            ThumbFill = new TextureRect
+            {
+                StyleClasses = { StyleClassThumbFill },
+                VerticalAlignment = VAlignment.Center,
+            };
+
+            ThumbOutline = new TextureRect
+            {
+                StyleClasses = { StyleClassThumbOutline },
+                VerticalAlignment = VAlignment.Center,
+            };
+
+            Symbol = new TextureRect
+            {
+                StyleClasses = { StyleClassSymbol },
+                VerticalAlignment = VAlignment.Center,
+            };
+
+            Label = new Label();
+            Label.Visible = false;
+
+            OffStateLabel = new Label();
+            OffStateLabel.Text = Loc.GetString("toggle-switch-default-off-state-label");
+            OffStateLabel.ReservesSpace = true;
+
+            OnStateLabel = new Label();
+            OnStateLabel.Text = Loc.GetString("toggle-switch-default-on-state-label");
+            OnStateLabel.ReservesSpace = true;
+            OnStateLabel.Visible = false;
+
+            Label.HorizontalExpand = true;
+
+            AddChild(Label);
+            AddChild(TrackFill);
+            AddChild(TrackOutline);
+            AddChild(ThumbFill);
+            AddChild(ThumbOutline);
+            AddChild(Symbol);
+            AddChild(OffStateLabel);
+            AddChild(OnStateLabel);
+        }
+
+        protected override void DrawModeChanged()
+        {
+            // Workaround for child controls not being updated automatically.
+            // Remove once https://github.com/space-wizards/RobustToolbox/pull/6264
+            // or similar is merged.
+            var relevantChangeMade = false;
+
+            if (Disabled)
+            {
+                if (!HasStylePseudoClass(StylePseudoClassDisabled))
+                {
+                    AddStylePseudoClass(StylePseudoClassDisabled);
+                    relevantChangeMade = true;
+                }
+            }
+            else
+            {
+                if (HasStylePseudoClass(StylePseudoClassDisabled))
+                {
+                    RemoveStylePseudoClass(StylePseudoClassDisabled);
+                    relevantChangeMade = true;
+                }
+            }
+
+            if (Pressed)
+            {
+                if (!HasStylePseudoClass(StylePseudoClassPressed))
+                {
+                    AddStylePseudoClass(StylePseudoClassPressed);
+                    relevantChangeMade = true;
+                }
+            }
+            else
+            {
+                if (HasStylePseudoClass(StylePseudoClassPressed))
+                {
+                    RemoveStylePseudoClass(StylePseudoClassPressed);
+                    relevantChangeMade = true;
+                }
+            }
+
+            if (relevantChangeMade)
+            {
+                Label.RemoveStyleClass("dummy");
+                TrackFill.RemoveStyleClass("dummy");
+                TrackOutline.RemoveStyleClass("dummy");
+                ThumbFill.RemoveStyleClass("dummy");
+                ThumbOutline.RemoveStyleClass("dummy");
+                Symbol.RemoveStyleClass("dummy");
+                OffStateLabel.RemoveStyleClass("dummy");
+                OnStateLabel.RemoveStyleClass("dummy");
+            }
+
+            // no base.DrawModeChanged() call - ContainerButton's pseudoclass handling
+            // doesn't support a button being both pressed and disabled
+
+            UpdateAppearance();
+        }
+
+        /// <summary>
+        ///     If true, the button will allow shrinking and clip text of the main
+        ///     label to prevent the text from going outside the bounds of the button.
+        ///     If false, the minimum size will always fit the contained text.
+        /// </summary>
+        [ViewVariables]
+        public bool ClipText { get => Label.ClipText; set => Label.ClipText = value; }
+
+        /// <summary>
+        ///     The text displayed by the button's main label.
+        /// </summary>
+        [ViewVariables]
+        public string? Text
+        {
+            get => Label.Text;
+            set
+            {
+                Label.Text = value;
+                Label.Visible = !string.IsNullOrEmpty(value);
+            }
+        }
+
+        /// <summary>
+        ///     The text displayed by the button's secondary label in the off state.
+        /// </summary>
+        [ViewVariables]
+        public string? OffStateText
+        {
+            get => OffStateLabel.Text;
+            set => OffStateLabel.Text = value;
+        }
+
+        /// <summary>
+        ///     The text displayed by the button's secondary label in the on state.
+        /// </summary>
+        [ViewVariables]
+        public string? OnStateText
+        {
+            get => OnStateLabel.Text;
+            set => OnStateLabel.Text = value;
+        }
+
+        private void UpdateAppearance()
+        {
+            if (OffStateLabel is not null)
+            {
+                OffStateLabel.Visible = !Pressed;
+            }
+
+            if (OnStateLabel is not null)
+            {
+                OnStateLabel.Visible = Pressed;
+            }
+        }
+
+        protected override void StylePropertiesChanged()
+        {
+            base.StylePropertiesChanged();
+            UpdateAppearance();
+        }
+
+        protected override Vector2 MeasureOverride(Vector2 availableSize)
+        {
+            var desiredSize = Vector2.Zero;
+            var separation = ActualSeparation;
+
+            // Start with the icon, since it always appears
+            if (TrackOutline is not null)
+            {
+                TrackOutline.Measure(availableSize);
+                desiredSize = TrackOutline.DesiredSize;
+            }
+
+            // Add space for the label if it has text
+            if (! string.IsNullOrEmpty(Label?.Text))
+            {
+                Label.Measure(availableSize);
+                desiredSize.X += separation + Label.DesiredSize.X;
+                desiredSize.Y = float.Max(desiredSize.Y, Label.DesiredSize.Y);
+            }
+
+            // Add space for the state labels if at least one of them has text
+            var stateLabelSpace = Vector2.Zero;
+            if (! string.IsNullOrEmpty(OffStateLabel?.Text))
+            {
+                OffStateLabel.Measure(availableSize);
+                stateLabelSpace = OffStateLabel.DesiredSize;
+            }
+
+            if (! string.IsNullOrEmpty(OnStateLabel?.Text))
+            {
+                OnStateLabel.Measure(availableSize);
+                stateLabelSpace.Y = float.Max(stateLabelSpace.Y, OnStateLabel.DesiredSize.Y);
+                stateLabelSpace.X = float.Max(stateLabelSpace.X, OnStateLabel.DesiredSize.X);
+            }
+
+            if (stateLabelSpace != Vector2.Zero)
+            {
+                desiredSize.X += separation + stateLabelSpace.X;
+                desiredSize.Y = float.Max(desiredSize.Y, stateLabelSpace.Y);
+            }
+
+            return desiredSize;
+        }
+
+        protected override Vector2 ArrangeOverride(Vector2 finalSize)
+        {
+            var separation = ActualSeparation;
+
+            var actualMainLabelWidth = finalSize.X - separation - TrackOutline.DesiredSize.X;
+            float iconPosition = 0;
+            float stateLabelPosition = 0;
+
+            if (string.IsNullOrEmpty(Label?.Text))
+            {
+                stateLabelPosition = TrackOutline.DesiredSize.X + separation;
+            }
+            else
+            {
+                if (!string.IsNullOrEmpty(OffStateLabel?.Text) || !string.IsNullOrEmpty(OnStateLabel?.Text))
+                {
+                    var stateLabelsWidth = float.Max(OffStateLabel!.DesiredSize.X, OnStateLabel.DesiredSize.X);
+                    actualMainLabelWidth -= (separation + stateLabelsWidth);
+                }
+                actualMainLabelWidth = float.Max(actualMainLabelWidth, 0);
+                iconPosition = actualMainLabelWidth + separation;
+                stateLabelPosition = iconPosition + TrackOutline.DesiredSize.X + separation;
+            }
+
+            var mainLabelTargetBox = new UIBox2(0, 0, actualMainLabelWidth, finalSize.Y);
+            Label?.Arrange(mainLabelTargetBox);
+
+            var iconTargetBox = new UIBox2(iconPosition, 0, iconPosition + TrackOutline.DesiredSize.X, finalSize.Y);
+            TrackFill.Arrange(iconTargetBox);
+            TrackOutline.Arrange(iconTargetBox);
+            Symbol.Arrange(iconTargetBox);
+
+            ThumbOutline.Measure(TrackOutline.DesiredSize); // didn't measure in MeasureOverride, don't need its size there
+            var thumbLeft = iconTargetBox.Left;
+            if (Pressed)
+                thumbLeft = iconTargetBox.Right - ThumbOutline.DesiredSize.X;
+            var thumbTargetBox = new UIBox2(thumbLeft, 0, thumbLeft + ThumbOutline.DesiredSize.X, finalSize.Y);
+            ThumbFill.Arrange(thumbTargetBox);
+            ThumbOutline.Arrange(thumbTargetBox);
+
+            var stateLabelsTargetBox = new UIBox2(stateLabelPosition, 0, finalSize.X, finalSize.Y);
+            OffStateLabel?.Arrange(stateLabelsTargetBox);
+            OnStateLabel?.Arrange(stateLabelsTargetBox);
+
+            return finalSize;
+        }
+    }
+}
index 20bb552d8912ced8d59e9a6d190f92699b97f912..575c6499f46f253548444d23b6aff49d33cddce1 100644 (file)
@@ -1,3 +1,7 @@
 ## Loc strings for generic "on/off button" control.
 ui-button-off = Off
 ui-button-on = On
+
+# These are for switch labels that indicate the current state
+toggle-switch-default-off-state-label = Off
+toggle-switch-default-on-state-label = On
index 125d77fa7ce1996a1f302e6190f3d757b563b380..066d3434f4d1606e2f8b20d61c01bea8e3b0a340 100644 (file)
@@ -1,6 +1,5 @@
 apc-menu-title = APC
 apc-menu-breaker-label = Main Breaker
-apc-menu-breaker-button = Toggle
 apc-menu-power-label = Load
 apc-menu-external-label = External Power
 apc-menu-charge-label = {$percent} Charged
diff --git a/Resources/Textures/Interface/Nano/attributions.yml b/Resources/Textures/Interface/Nano/attributions.yml
new file mode 100644 (file)
index 0000000..079a7e3
--- /dev/null
@@ -0,0 +1,9 @@
+- files: ["switchbutton_symbol_off.svg",
+          "switchbutton_symbol_on.svg",
+          "switchbutton_thumb_fill.svg",
+          "switchbutton_thumb_outline.svg",
+          "switchbutton_track_fill.svg",
+          "switchbutton_track_outline.svg"]
+  license: "CC-BY-SA-3.0"
+  copyright: "Design by Janet Blackquill <uhhadd@gmail.com>"
+  source: "https://github.com/sowelipililimute"
diff --git a/Resources/Textures/Interface/Nano/switchbutton_symbol_off.svg b/Resources/Textures/Interface/Nano/switchbutton_symbol_off.svg
new file mode 100644 (file)
index 0000000..a860e66
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="48"
+   height="26"
+   viewBox="0 0 48 26"
+   version="1.1"
+   id="svg1"
+   inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
+   sodipodi:docname="switchbutton_label_off.svg"
+   inkscape:export-filename="switchbutton_symbol_off.svg.96dpi.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96"
+   xml:space="preserve"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+     id="namedview1"
+     pagecolor="#7f7f7f"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:document-units="px"
+     showguides="false"
+     inkscape:zoom="12.618721"
+     inkscape:cx="19.138231"
+     inkscape:cy="15.215488"
+     inkscape:window-width="1600"
+     inkscape:window-height="828"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     showgrid="true"><inkscape:grid
+       id="grid2"
+       units="px"
+       originx="2.220446e-16"
+       originy="0"
+       spacingx="1"
+       spacingy="1"
+       empcolor="#0099e5"
+       empopacity="0.30196078"
+       color="#0099e5"
+       opacity="0.14901961"
+       empspacing="5"
+       enabled="true"
+       visible="true" /></sodipodi:namedview><defs
+     id="defs1" /><g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"><circle
+       style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
+       id="path4"
+       cx="34"
+       cy="13"
+       r="5" /></g></svg>
diff --git a/Resources/Textures/Interface/Nano/switchbutton_symbol_off.svg.96dpi.png b/Resources/Textures/Interface/Nano/switchbutton_symbol_off.svg.96dpi.png
new file mode 100644 (file)
index 0000000..bf8a390
Binary files /dev/null and b/Resources/Textures/Interface/Nano/switchbutton_symbol_off.svg.96dpi.png differ
diff --git a/Resources/Textures/Interface/Nano/switchbutton_symbol_on.svg b/Resources/Textures/Interface/Nano/switchbutton_symbol_on.svg
new file mode 100644 (file)
index 0000000..e49da31
--- /dev/null
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="48"
+   height="26"
+   viewBox="0 0 48 26"
+   version="1.1"
+   id="svg1"
+   sodipodi:docname="switchbutton_symbol_on.svg"
+   inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
+   inkscape:export-filename="switchbutton_symbol_on.svg.96dpi.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96"
+   xml:space="preserve"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+     id="namedview1"
+     pagecolor="#7f7f7f"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:document-units="px"
+     showguides="true"
+     inkscape:zoom="6.3093604"
+     inkscape:cx="14.977746"
+     inkscape:cy="-1.7434414"
+     inkscape:window-width="1600"
+     inkscape:window-height="828"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     showgrid="true"><inkscape:grid
+       id="grid1"
+       units="px"
+       originx="1.1581421e-08"
+       originy="0.0059233303"
+       spacingx="1"
+       spacingy="1"
+       empcolor="#0099e5"
+       empopacity="0.30196078"
+       color="#0099e5"
+       opacity="0.14901961"
+       empspacing="5"
+       enabled="true"
+       visible="true" /></sodipodi:namedview><defs
+     id="defs1" /><g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,0.00592333)"><path
+       style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;paint-order:stroke fill markers;fill-opacity:1"
+       d="M 15,6.9999999 V 19"
+       id="path1" /></g></svg>
diff --git a/Resources/Textures/Interface/Nano/switchbutton_symbol_on.svg.96dpi.png b/Resources/Textures/Interface/Nano/switchbutton_symbol_on.svg.96dpi.png
new file mode 100644 (file)
index 0000000..4d1e8e2
Binary files /dev/null and b/Resources/Textures/Interface/Nano/switchbutton_symbol_on.svg.96dpi.png differ
diff --git a/Resources/Textures/Interface/Nano/switchbutton_thumb_fill.svg b/Resources/Textures/Interface/Nano/switchbutton_thumb_fill.svg
new file mode 100644 (file)
index 0000000..c4baeec
--- /dev/null
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="24"
+   height="26.000452"
+   viewBox="0 0 24 26.000452"
+   version="1.1"
+   id="svg1"
+   sodipodi:docname="switchbutton_thumb_fill.svg"
+   inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
+   inkscape:export-filename="switchbutton_thumb_fill.svg.96dpi.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96"
+   xml:space="preserve"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+     id="namedview1"
+     pagecolor="#7f7f7f"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:document-units="px"
+     showguides="true"
+     inkscape:zoom="6.3093604"
+     inkscape:cx="14.977746"
+     inkscape:cy="10.936132"
+     inkscape:window-width="1600"
+     inkscape:window-height="828"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     showgrid="true"><inkscape:grid
+       id="grid1"
+       units="px"
+       originx="1.1581421e-08"
+       originy="3.8550387e-06"
+       spacingx="1"
+       spacingy="1"
+       empcolor="#0099e5"
+       empopacity="0.30196078"
+       color="#0099e5"
+       opacity="0.14901961"
+       empspacing="5"
+       enabled="true"
+       visible="true" /></sodipodi:namedview><defs
+     id="defs1" /><g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"><path
+       style="baseline-shift:baseline;display:inline;overflow:visible;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
+       d="M 1,1 V 15.576073 L 10.419922,24.99408 23,24.99998 V 10.419969 L 13.582031,1.0000485 Z"
+       id="path3-7-0"
+       sodipodi:nodetypes="ccccccc" /></g></svg>
diff --git a/Resources/Textures/Interface/Nano/switchbutton_thumb_fill.svg.96dpi.png b/Resources/Textures/Interface/Nano/switchbutton_thumb_fill.svg.96dpi.png
new file mode 100644 (file)
index 0000000..5863b38
Binary files /dev/null and b/Resources/Textures/Interface/Nano/switchbutton_thumb_fill.svg.96dpi.png differ
diff --git a/Resources/Textures/Interface/Nano/switchbutton_thumb_outline.svg b/Resources/Textures/Interface/Nano/switchbutton_thumb_outline.svg
new file mode 100644 (file)
index 0000000..f57579d
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="24"
+   height="26.000452"
+   viewBox="0 0 24 26.000453"
+   version="1.1"
+   id="svg1"
+   sodipodi:docname="switchbutton_thumb_outline.svg"
+   inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
+   inkscape:export-filename="switchbutton_thumb_outline.svg.96dpi.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96"
+   xml:space="preserve"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+     id="namedview1"
+     pagecolor="#7f7f7f"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:document-units="px"
+     showguides="true"
+     inkscape:zoom="12.618721"
+     inkscape:cx="14.779628"
+     inkscape:cy="12.124842"
+     inkscape:window-width="1600"
+     inkscape:window-height="828"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     showgrid="true"><inkscape:grid
+       id="grid1"
+       units="px"
+       originx="-24"
+       originy="3.8764731e-06"
+       spacingx="1"
+       spacingy="1"
+       empcolor="#0099e5"
+       empopacity="0.30196078"
+       color="#0099e5"
+       opacity="0.14901961"
+       empspacing="5"
+       enabled="true"
+       visible="true" /></sodipodi:namedview><defs
+     id="defs1" /><g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-24.000001)"><path
+       style="baseline-shift:baseline;display:inline;overflow:visible;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
+       d="M 25.000001,0.99999998 V 15.576073 l 9.419922,9.418007 12.580078,0.0059 V 10.419969 L 37.582032,1.0000485 Z"
+       id="path3-7"
+       sodipodi:nodetypes="ccccccc" /></g></svg>
diff --git a/Resources/Textures/Interface/Nano/switchbutton_thumb_outline.svg.96dpi.png b/Resources/Textures/Interface/Nano/switchbutton_thumb_outline.svg.96dpi.png
new file mode 100644 (file)
index 0000000..ef74d53
Binary files /dev/null and b/Resources/Textures/Interface/Nano/switchbutton_thumb_outline.svg.96dpi.png differ
diff --git a/Resources/Textures/Interface/Nano/switchbutton_track_fill.svg b/Resources/Textures/Interface/Nano/switchbutton_track_fill.svg
new file mode 100644 (file)
index 0000000..102fb37
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="48"
+   height="26.000023"
+   viewBox="0 0 48 26.000023"
+   version="1.1"
+   id="svg1"
+   sodipodi:docname="switchbutton_track_fill.svg"
+   inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
+   inkscape:export-filename="switchbutton_track_fill.svg.96dpi.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96"
+   xml:space="preserve"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+     id="namedview1"
+     pagecolor="#7f7f7f"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:document-units="px"
+     showguides="true"
+     inkscape:zoom="6.3093604"
+     inkscape:cx="14.977746"
+     inkscape:cy="10.936132"
+     inkscape:window-width="1600"
+     inkscape:window-height="828"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     showgrid="true"><inkscape:grid
+       id="grid1"
+       units="px"
+       originx="1.1581421e-08"
+       originy="0.0059233341"
+       spacingx="1"
+       spacingy="1"
+       empcolor="#0099e5"
+       empopacity="0.30196078"
+       color="#0099e5"
+       opacity="0.14901961"
+       empspacing="5"
+       enabled="true"
+       visible="true" /></sodipodi:namedview><defs
+     id="defs1" /><g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,0.00592333)"><path
+       style="baseline-shift:baseline;display:inline;overflow:visible;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
+       d="M 1,1.0000182 V 15.576092 L 10.419922,24.9941 47,24.994096 l 10e-7,-14.580032 -9.417969,-9.4199208 z"
+       id="path3-7"
+       sodipodi:nodetypes="ccccccc" /></g></svg>
diff --git a/Resources/Textures/Interface/Nano/switchbutton_track_fill.svg.96dpi.png b/Resources/Textures/Interface/Nano/switchbutton_track_fill.svg.96dpi.png
new file mode 100644 (file)
index 0000000..2e91c2d
Binary files /dev/null and b/Resources/Textures/Interface/Nano/switchbutton_track_fill.svg.96dpi.png differ
diff --git a/Resources/Textures/Interface/Nano/switchbutton_track_outline.svg b/Resources/Textures/Interface/Nano/switchbutton_track_outline.svg
new file mode 100644 (file)
index 0000000..60b2ca4
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="48"
+   height="26.000023"
+   viewBox="0 0 48 26.000024"
+   version="1.1"
+   id="svg1"
+   sodipodi:docname="switchbutton_track_outline.svg"
+   inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
+   inkscape:export-filename="switchbutton_track_outline.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96"
+   xml:space="preserve"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+     id="namedview1"
+     pagecolor="#7f7f7f"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:document-units="px"
+     showguides="true"
+     inkscape:zoom="8"
+     inkscape:cx="15.3125"
+     inkscape:cy="34.25"
+     inkscape:window-width="1600"
+     inkscape:window-height="828"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     showgrid="true"><inkscape:grid
+       id="grid1"
+       units="px"
+       originx="1.1581421e-08"
+       originy="0.0059415338"
+       spacingx="1"
+       spacingy="1"
+       empcolor="#0099e5"
+       empopacity="0.30196078"
+       color="#0099e5"
+       opacity="0.14901961"
+       empspacing="5"
+       enabled="true"
+       visible="true" /></sodipodi:namedview><defs
+     id="defs1" /><g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,0.00594153)"><path
+       style="baseline-shift:baseline;display:inline;overflow:visible;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
+       d="M 1,0.99999997 V 15.576073 l 9.419922,9.418008 36.580078,-4e-6 10e-7,-14.580031 -9.417969,-9.419921 z"
+       id="path3-7"
+       sodipodi:nodetypes="ccccccc" /></g></svg>
diff --git a/Resources/Textures/Interface/Nano/switchbutton_track_outline.svg.96dpi.png b/Resources/Textures/Interface/Nano/switchbutton_track_outline.svg.96dpi.png
new file mode 100644 (file)
index 0000000..2fab921
Binary files /dev/null and b/Resources/Textures/Interface/Nano/switchbutton_track_outline.svg.96dpi.png differ