<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"
<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>
private void OnDisplayTrackNamesPressed(BaseButton.ButtonEventArgs obj)
{
- DisplayTrackNames.SetClickPressed(!DisplayTrackNames.Pressed);
Populate();
}
<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">
<!-- 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'}" />
{
var castState = (ApcBoundInterfaceState) state;
- if (!BreakerButton.Disabled)
- {
- BreakerButton.Pressed = castState.MainBreaker;
- }
+ BreakerButton.Pressed = castState.MainBreaker;
if (PowerLabel != null)
{
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.
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");
}
--- /dev/null
+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; }
+}
+
--- /dev/null
+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),
+ ];
+ }
+}
--- /dev/null
+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;
+ }
+ }
+}
## 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
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
--- /dev/null
+- 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"
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>