Socially Distant OS
  • Docs
  • API
Search Results for

    Show / Hide Table of Contents
    • Accessibility
      • In-Game TTS
    • Development and Modding: Getting started
      • Building from source
      • Contribution guidelines
      • Project structure
      • Code style guide
    • Game scripting (sdsh)
      • The Basics
    • Game Framework
      • Event Bus
      • Playing Audio
    • Story Scripting
      • Career Mode Narrative Objects
      • Narrative Identifiers
      • News Articles
    • User Interface
      • UI Overview
      • Signals
      • List Adapters
      • Optimizing UI for Performance
      • Advanced UI features
        • Visual Styles

    UI Overview

    Socially Distant uses a custom widget-based UI system that prioritizes automatic layout over manual layout. The UI system is built specifically to meet the game's advanced needs. Nonetheless, it's fairly simple to work with. This page is an overview of the most common widget types, and how everything fits together in the game.

    Visual Styles

    In Socially Distant, and in Ritchie's Toolbox in general, widgets do not have a defined look by default. It is up to the game to provide a global visual style for all widgets. Socially Distant provides SociallyDistantVisualStyle as the UI's global visual style.

    The game must define a global visual style. However, the game can also define widget-specific visual styles. This is useful for creating a visual style for an in-game website that gives the website its own distinct look and feel from the rest of the in-game OS's programs. When setting a custom visual style on a widget, all children of that widget inherit the custom visual style.

    *️⃣ Note

    Because widgets don't have a defined look without a Visual Style to dictate it, widgets also don't have a default font for text rendering. It is up to the Visual Style to provide all fonts used in the UI, with the exception of custom fonts set on specific widgets.

    The Visual Style system should never be worked around. If you're designing a custom widget, you should integrate it with the game's Visual Style as much as you can. This allows your custom widget to be reusable, themeable, and to reman consistent within the game's art style. Consider modifying SociallyDistantVisualStyle to do the rendering for your custom widget if you are contributing to the base game.

    Widgets

    Socially Distant's user interface is made of widgets. Widgets are assembled like building blocks to make more complex widgets, which can be further assembled together into more complex widrgets. Most widgets in the game are just groups of smaller widgets arranged in a certain way to accomplish a reusable layout.

    Custom Properties

    Every widget has a set of properties shared across all widgets. Each specific widget also has a set of its own specific properties. For example, all widgets have a HorizontalAlignment property while only TextWidget s have a TextAlign property. You can get far with this. However, there are cases where that's not enough. Sometimes, a parent widget needs to know something about each of its child widgets that isn't common across all widget types. Sometimes, the Visual Style may support drawing widgets in a certain way but there's no way for it to know whether you want it to do that. This is where Custom Properties come in. All widgets support them.

    Custom Properties is a system that lets you assign custom data to a widget. This data can then be picked up by some other part of the game. For example, to make a widget draw with a red background:

    var widget = new Box();
    widget.SetCustomProperty(WidgetBackgrounds.Common);
    widget.SetCustomProperty(CommonColor.Red);
    

    The above code tells SociallyDistantVisualStyle to use a common color for the widget's background, and that the color should be the game's red accent color.

    There are many more uses of custom properties, as you discover more of the UI system.

    Input Events

    Unlike Windows Forms and WPF, and most other UI systems, not all widgets are aware of input events such as mouse movement or key presses. Widgets only listen to the input events they need to, and only fire events that they want to. For example, an input field does not fire a KeyChar event - rather it tells you when the input field's value has been changed or submitted.

    If you need a widget to respond to an input event, you must wrap it in a parent widget that listens for the given input event. This means either using an existing widget, like Button, that handles the inputs you need. Otherwise, you need to create a custom widget.

    When creating a custom widget, you inform the UI system of the input events you care about by implementing them as interfaces. The following interfaces can be implemented by a Widget class to receive input events.

    Mouse

    • IMouseEnterHandler: Receive an event when the mouse cursor enters the widget's content area.
    • IMouseLeaveHandler: Receive an event when the mouse cursor leaves the widget's content area.
    • IMouseMoveHandler: Receive an event every time the mouse cursor moves within the widgets' content area.
    • IMouseDownHandler: Receive an event when a mouse button is pressed on the widget.
    • IMouseUpHandler: Receive an event when a mouse button is released on the widget.
    • IMouseClickHandler: Receive an event when a mouse button is pressed and then released on the widget.
    • IMouseScrollHandler: Receive an event when the mouse wheel is scrolled on the widget.

    Drag and Drop

    • IDragStartHandler: Receive an event when the widget starts being dragged by the mouse.
    • IDragHandler: Receive an event while the mouse continues to drag the widget.
    • IDragEndHandler: Receive an event when the mouse stops dragging the widget.

    Keyboard

    • IKeyDownHandler: Receive an event when a key is pressed or held, if the widget is in focus.
    • IKeyUpHandler: Receive an event when a key is released, if the widget is in focus.
    • IKeyCharHandler: Receive an event when text is typed, if the widget is in focus.

    Preview Keyboard

    • IPreviewKeyDownHandler: Receive an event when a key is pressed or held, even if the widget isn't in focus.
    • IPreviewKeyUpHandler: Receive an event when a key is released, even if the widget isn't in focus.
    • IPreviewKeyCharHandler: Receive an event when text is typed, even if the widget isn't in focus.

    Keyboard Focus

    • IGainFocusHandler: Receive an event when the widget, or one of its children, gains keyboard focus.
    • ILoseFocusHandler: Receive an event when the widget, or one of its children, loses keyboard focus.

    Special

    • IUpdateHandler: Receive an event every frame.

    When a widget receives any of the above events, the event is bubbled to the first parent who listens to the same event type. This bubbling repeats until the UI system reaches a widget with no parent. A widget can prevent the event from propagating to its parent by calling e.Handle() on the event.

    A widget can also request keyboard focus in response to an event by calling e.RequestFocus(). For example, the InputField widget requests focus when you click on it. Requesting focus also prevents the event from propagating to a parent.

    Types of widgets

    Base widgets

    Base widgets are widgets you can't directly use as UI elements. Rather, they serve as building blocks for actual UI elements, providing their common base functionality. You can write your own, but there are three types of base widgets that serve most needs.

    Widget

    This is the base class of all widgets, including other base widgets. It provides the default layout and rendering behaviour for all widgets, and several properties that all widgets must have.

    • MinimumSize: The minimum size, in pixels, that a widget must take within its container.
    • MaximumSize: The maximum size, in pixels, that a widget is allowed to take within its container.
    • Parent?: A reference to the widget's parent widget. Can be null.
    • RenderEffect?: A reference to an object implementing IWidgetEffect that can be used to apply visual effects to a widget's geometry. Can be null.
    • VisualStyle? : A reference to an object implementing IVisualStyle that can be set to override the game's global visual style. Can be null and should be rarely used.
    • HorizontalAlignment: Defines the widget's horizontal alignment (left, center, right, or stretch) within the space given to it by its parent widget. Default is Stretch.
    • VerticalAlignment: Defines the widget's vertical alignment (top, middle, right, stretch) within the space given to it by its parent widget. Default is Stretch.
    • Padding: An amount, in pixels, for each cardinal direction, of space between a given edge of the widget and the corresponding edge of the space given to the widget by its parent.
    • Margin: An amount, in pixels, for each cardinal direction, of space between a given edge of the widget and the corresponding edge of the widget's content/children.
    • RenderOpacity: The visual translucency, between 0 and 1, of the widget and its children. Default is 1.
    • ContentArea: The calculated position and size of the widget, expressed as a layout rectangle, as of the last layout update.
    • Enabled : A value indicating whether the widget is enabled or disabled. If disabled, the widget appears visually grayed out and doesn't receive any input events. Default is enabled.
    • Visibility: A value defining the widget's visibility (visible, hiddem, or collapsed). If visible, the widget contributes to layout and renders. If hidden, the widget contributes to layout but doesn't render. If collapsed, the widget doesn't render or contribute to layout. Default is visible.

    ContentWidget

    A ContentWidget is just a Widget that can contain a single child. The most widely-used examples are Box, Button , and InfoBox. Although users of a ContentWidget can only assign one child to the widget, the widget itself may add multiple children. This is how InfoBox is able to have a decorative colored strip as well as a title.

    The only special property of ContentWidget is Content, which is a reference to a widget. Setting Content to another widget will assign that widget as the child of the ContentWidget.

    ContainerWidget

    A ContainerWidget is a Widget that can contain multiple child widgets. The most common examples are FlexPanel, StackPanel, ScrollView, WrapPanel and OverlayPanel. Like a ContentWidget, a ContainerWidget may contain more children that cannot be directly accessed (such as the "new tab" button on window tab lists).

    The only special property of a ContainerWidget is its ChildWidgets collection. Use this to add, remove, and otherwise access the widget's children.

    UI Elements

    These widgets are all building blocks for more complex user interfaces in the game.

    TextWidget

    TextWidget is an extremely-common, and extremely versatile text renderer for the entire UI system. It ain't your grandma's text label, as it supports rich text markup and even images.

    The most important properties of a TextWidget are:

    • Text: The text displayed in the widget.
    • TextAlign: The horizontal alignment of the text (left, center, right) within the widget's content area. Defaults to left.
    • WordWrapped: Whether the text is word-wrapped within the widget's content area. Defaults to false for performance reasons.
    • UseMarkup: Whether the widget uses rich text markup. Defaults to false for performance reasons.
    • ShowMarkup: When UseMarkup is turned on, determines whether the raw markup is displayed. Defaults to false, and should generally only be used when writing input fields.
    • Color?: The color (or, when in markup mode, default color) of text. Can be null. If set to null, the color comes from the widget's visual style. Defaults to null.
    • FontSize?: The font size (or, when in markup mode, default font size), in pixels, of the text. Can be null. If null, the value comes from the visual style. Defaulrs to null.
    • FontWeight?: Determines the weight (boldness) of the text. If in markup mode, the <b> tag can override this to FontWeight.Bold. Defaults to FontWeight.Normal.
    • Font?: The font style of the text. Can be null. If null, the font style comes from the visual style. Defaults to null.

    InputField

    This is a customizable text entry field using TextWidget for its display. You can control most of the same properties you can with TextWidget itself. You can also control how the Enter Key and mouse behave in the input field. These are the most important properties of the input field:

    • Value: The current value of the input field.
    • Placeholder: Text to display in the input field when it's empty.
    • Multiline: Whether the text can occupy multiple lines. Defaults to false.
    • WordWrapped: Whether the input field wraps its display horizontally or scrolls it. Defaults to false

    Toggle

    Toggles are basic on/off switches. They can be displayed as checkboxes or left/right toggle switches (this is purely cosmetic, they function identically).

    • UseSwitchVariant: Whether the toggle is a checkbox or a switch.
    • ToggleValue: The current toggle value, on or off.

    Button

    A clickable surface. The button doesn't visually alter itself or its children in any way, unless the visual style decides to render a button background (Socially Distant does not). The Button is a ContentWidget, and is used to make any other widget clickable. This is useful for creating custom UI elements where you need a clickable area (such as the close button on a tab), but want to deal with visual interactions yourself.

    StringDropdown

    A simple selection dropdown that presents a list of strings to the user and allows them to select one. The code can be used as a reference implementation for other Dropdown-based widgets, if you need a more-advanced item display than just text. However, in most cases, StringDropdown is all you need.

    Icon

    A widget that can be used to display a Unicode text icon at any size, using the visual style's icon font. In Socially Distant, this is used to display Material Design icons.

    Image

    A widget used to display a picture or other texture.

    Slider

    A widget used to allow the user to input a ranged number value. The slider can be vertical or horizontal, and the mouse is used to drag the slider between its minimum and maximum values.

    Layout Widgets

    Layout Widgets are ContainerWidget widgets that apply a specific layout algorithm to their children. When putting complex UIs together, layout widgets are the most useful tool you have.

    StackPanel

    StackPanel arranges its children in a stack, either vertically or horizontally. You can define the direction (vertical by default), and the spacing between each child (in pixels, 0 by default). StackPanel is the most common layout widget used in the game.

    WrapPanel

    WrapPanel behaves similarly to StackPanel. The main difference is its children wrap to a new line when they can no longer fit on the current line. For this reason, you can set both the horizontal and vertical spacing between children.

    If the wrap panel's direction is vertical, children are arranged top-to-bottom and wrap at the bottom edge. When in horizontal mode, children are arranged left-to-right and wrap at the right edge.

    Wrap Panels are used for window tab lists and file grids.

    FlexPanel

    FlexPanel is a basic implementation of the flexbox algorithm. Children can either be auto-sized or sized proportionally within the flex panel. Other than that, it has the same properties as StackPanel.

    To change the sizing behaviour of a FlexPanel's child, you use the FlexPanelProperties custom property. For example, to make a child fill 100% of the flex panel's remaining space:

    var flex = new FlexPanel();
    var widget = new Box();
    flex.ChildWidgets.Add(widget);
    
    var flexSettings = widget.GetCustomProperties<FlexPanelProperties>();
    flexSettings.Mode = SizeMode.Proportional;
    flexSettings.Percentage = 1f;
    

    FlexPanels are used by the game's Status Bar, Dock, window title areas, and to arrange almost all of the game's top-level layout.

    ScrollView

    ScrollView behaves identically to a vertical StackPanel, but scrolls its children vertically. It can also render a scrollbar if its children don't fit within the ScrollView's content area. The ScrollView shrinks to fit its children, and expands until its children can't fit within the ScrollView's parent.

    ⚠️ Warning

    Adding a ScrollView as a child of a FlexPanel can cause some NASTY layout bugs if you don't do it correctly. You must either set the ScrollView's flex mode to proportional, or make sure there's a mimimum width and maximum height set on the Scrollview or one of its parents.

    OverlayPanel

    OverlayPanel just allows you to overlay multiple children on top of each other using the default layout algorithm of all Widgets. This is used by the game's modal dialogs, as well as by the cards in Info Panel to display a close button in their top-right corner.

    Shell Widgets

    Shell Widgets are custom widgets provided by Socially Distant (and not Ritchie's Toolbox) that implement game-specific needs.

    SimpleTerminal

    A port of the suckless simple terminal emulator (st). It's used by the game's Terminal, and is by no means simple to use.

    TextButton

    A simple clickable button with centered text on it.

    CompositeIconWidget

    Used to draw a CompositeIcon value, which can be either a texture or a Material icon. This is used for window icons, Dock icons, and file icons.

    ToolbarIcon

    A clickable CompositeIconWidget. Used for the navigation buttons in File Manager and Web Browser's toolbar.

    InfoBox

    A box with a colored decorative strip, optional title, and content. Used by modal question/info dialogs, email messages, and Markdown blockquotes.

    ListItem

    A clickable and selectable widget. Used for various item lists in the game, like the player's email inbox, DM list, and the Category list in System Settings.

    ListItemWithHeader

    A ListItem with a dark gray text label above it. Used in various sectioned lists like the Categories list in System Settings.

    Writing a custom Widget

    If you find yourself creating the same set of widgets over and over again, and laying them out the same way, then it's time for you to create a custom widget. So here's how. Let's create a simple widget that displays an icon and a text label next to it, like a FileGrid does.

    First, we define a custom class inheriting Widget.

    public class FileIcon : Widget
    {
    
    }
    

    Next, declare and instantiate all of the child widgets you'll need to create this file icon layout. We'll need an icon, a label, and a way to stack them next to each other.

    public class MyWidget : Widget
    {
        private readonly StackPanel          root  = new();
        private readonly CompositeIconWidget icon  = new();
        private readonly TextWidget          label = new();
    }
    

    We must add the root of our custom widget as a child to the custom widget itself. If we were just instantiating MyWidget, we wouldn't be able to add children to it because Widget.Children is protected to prevent you from adding children to widgets where that doesn't make sense. So, we must add our custom widget's children within the MyWidget constructor.

    public MyWidget()
    {
        Children.Add(root);
    }
    

    *️⃣ Code review note

    It is a good practice to name the root widget of your custom widget root, and to make sure it is the first widget instantiated and added. This makes it clear that you are building a custom widget that just uses other widgets as building blocks. This also means you can change the type of layout widget used later.

    Next, add the icon and label as child widgets to the root widget.

    root.ChildWidgets.Add(icon);
    root.ChildWidgets.Add(label);
    

    You can now instantiate and use MyWidget inside other widgets! It's up to you to expose all of the properties, events, and public methods you need to control the custom widget. You can also set any visual and layout properties you need to during the MyWidget constructor, for example:

    public MyWidget()
    {
        root.Direction = Direction.Horizontal;
        root.Spacing = 3;
        root.Padding = 3;
    
        label.VerticalAlignment = VerticalAlignment.Middle;
        icon.VerticalAlignment = VerticalAlignment.Middle;
    
        icon.IconSize = 16;
    
        label.WordWrapped = true;
    
        Children.Add(root);
        root.ChildWidgets.Add(icon);
        root.ChildWidgets.Add(label);
    }
    

    Further Reading

    This should cover the basics of using the UI system, and should give plenty of common widgets to work with. However, you might want to learn more things you can do.

    • Learn how to add programs and websites to Socially Distant
    • Learn how to optimize UI for performance
    • Learn how to create list views with ListAdapter
    • Learn how to create cystom dropdowns
    In this article
    Back to top Generated by DocFX