Developer Portal
Quick StartsComposition scriptingAPIs and SDKsSupport
  • Portal overview
  • Quick start
  • REST API
    • Introduction
    • Rate limits
    • Authorization
    • How-to guides
      • Get a control app's API token
      • Get a composition's sub-composition IDs and names and their payload structures
      • Get a control app's model
      • Get a control app's metadata
      • Update a sub-composition's content
      • Update a sub-composition's animation state
      • Update a sub-composition's content and animation state in one call
      • Update multiple sub-compositions in one call
    • API reference
      • Get control app details
        • Get a control app's metadata
        • Get a control app's model
        • Get a control app's control data
      • Send data to a control app
        • Update a control app's content
        • Update a control app's animation state
      • Take out all of an app's output
  • Data stream API
    • Introduction
    • Rate limits
    • Authorization
    • How-to guides
      • Create a data stream
      • Link a data stream to a composition
      • Send data to an app using the data stream API
    • API reference
  • Composition scripting
    • Introduction
    • Overview
    • Quick start
      • Find sub-compositions and widgets
      • Read and update control nodes
      • Set text widget text properties
      • Read and update widget properties
      • Read control nodes and update widget properties
      • Set image widget URL property
      • Set table widget content property
    • Cheat sheets
      • Fundamentals
      • Interactive overlays
      • Best practices
    • Use cases
      • Read control nodes and generate HTML text
      • Read control nodes, generate HTML text with background
      • Text Ticker - Start ticker on "In" animation
    • Composition script editor reference
  • Software development kits
    • Graphics SDK
      • Getting started
      • Reference
        • SDK functions
        • Composition object
        • Sequencer object
      • Guides and examples
        • Load a composition with its token
        • Load a composition with its URL
        • Get the composition URL of an app instance
        • Sequencer VOD example
        • Control local preview of app
        • Load app instance output
    • Overlay SDK
      • Getting started
      • SDK functions
      • Use case examples
    • Widget SDK
      • Preparing your environment
      • Getting started
      • Reference
        • Widget UI definition
        • Widget callback functions
        • Time control object
        • Composition instance
      • Guides and examples
        • Widget example: CSS patterns
    • App SDK
  • Singular Basics
    • Overview of Singular
    • Managing overlays in the Dashboard
      • How to create a new composition
      • How to open a new app template
      • How to create an app for a composition
      • How to extract a composition from an app
      • How to find an app's shared app token and shared API URL
      • Dashboard reference
    • Building overlays in Composer
      • How to build a composition
      • How to set up layer logic to automate overlay transitions
      • How to set up control nodes to make widget properties available to a control app
      • Animating overlays
        • How to create timeline animations
        • How to create behavior animations
        • How to create update animations
      • How to make overlays interactive
      • How to adapt overlays to various screen sizes
      • Composer reference
    • Controlling overlays in Studio and UNO
      • How to use Studio
      • Studio reference
      • UNO reference
  • Support
    • Singular status
    • Support resources
    • Singular terminology
    • Performance Testing
Powered by GitBook
On this page
  • Widget UI
  • Widget code

Was this helpful?

  1. Software development kits
  2. Widget SDK
  3. Guides and examples

Widget example: CSS patterns

This example widget lets users choose a pattern from a selection and define colors and spacing.

PreviousGuides and examplesNextApp SDK

Last updated 2 years ago

Was this helpful?

Widget UI

The widget UI exposes the following properties and groups them in a single card.

Name
Id
Type
Description

Select Pattern

patternId

selection

Selection of pattern names

Back Color

backColorId

color

Background color

Front Color

frontColorId

color

Foreground color

Spacing

spacingId

number

Spacing between pattern repetitions

UI definition
{
  "model": {
    "fields": [
      {
        "id": "patternId",
        "type": "selection",
        "title": "Select Pattern",
        "defaultValue": "Rectangles",
        "selections": [
          {
            "id": "Circles",
            "title": "Circles"
          },
          {
            "id": "Rectangles",
            "title": "Rectangles"
          },
          {
            "id": "Triangles",
            "title": "Triangles"
          }
        ]
      },
      {
        "id": "backColorId",
        "type": "color",
        "title": "Back Color",
        "defaultValue": "#E5E5F7"
      },
      {
        "id": "frontColorId",
        "type": "color",
        "title": "Front Color",
        "defaultValue": "#444CF7"
      },
      {
        "id": "spacingId",
        "type": "number",
        "title": "Spacing",
        "defaultValue": "25",
        "min": "1",
        "max": "100",
        "step": "0.1",
        "unit": "unit",
        "format": "0.1"
      }
    ],
    "groups": [
      {
        "id": "group1Id",
        "title": "Pattern Options",
        "width": "single",
        "toolTip": "Select a Pattern and modify options",
        "childIds": ["patternId", "backColorId", "frontColorId", "spacingId"]
      }
    ]
  }
}

Widget code

The widget loads and uses the tinycolor.js library to convert various color values (RGB, HEX, HSL, RGBA, or HSLA) to HEX color definition.

output.html
<!DOCTYPE html>
<html>
  <head>
    <style>
      /* Default widget style to make output match the bouding box */
      html,
      body {
        background: none;
        margin: 0;
        padding: 0;
        height: 100%;
        overflow: hidden;
      }
      /* Add your custom styles here */
    </style>
  </head>
  <body>
    <!-- Your custom html goes here -->
    <div id="pattern" style="position: absolute; left:0%; top:0%; width: 100%; height: 100%"></div>
    
    <!-- Singular Widget Library -->
    <script src="https://app.singular.live/libs/singularwidget/1.0.4/singularwidget.js"></script>
    <!-- Load tinycolor library from cloudflare cdn -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/tinycolor/1.5.2/tinycolor.min.js" integrity="sha512-gV2/CUt/1tn0eFseTyMjNqZU3kvhXacxA5eFoTwTd9c4b/Ems0BF00LOG/KcWnGZRdo62tqYNS8IiKtU4PGVoA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script>
      let windowWidth = 0;
      let windowHeight = 0;
      let elePattern = undefined;
      let patternChanged = false;
      let pattern = "Rectangles";
      let spacing = 50;
      let bgColor = "#E5E5F7";
      let fgColor = "#444CF7";
      // we define pattern styles in a map
      const patternStyles = {
        "Circles": {
          draw: (params) => {
            const cssBgImage = "radial-gradient(circle at center center, {{fgColor}}, {{bgColor}}), repeating-radial-gradient(circle at center center, {{fgColor}}, {{fgColor}}, {{spacing}}%, transparent {{spacing2x}}%, transparent {{spacing}}%)";
            elePattern.style.backgroundImage = cssBgImage.replaceAll("{{fgColor}}", params.fgColor).replaceAll("{{bgColor}}", params.bgColor).replaceAll("{{spacing2x}}", params.spacing * 2).replaceAll("{{spacing}}", params.spacing);
            elePattern.style.backgroundBlendMode = "multiply";
          }
        },
        "Rectangles": {
          draw: (params) => {
            const cssBackground = "repeating-conic-gradient({{bgColor}} 0% 25%, {{fgColor}} 0% 50%) 50%/{{spacing}}vw {{spacing}}vh";
            elePattern.style.background = cssBackground.replaceAll("{{fgColor}}", params.fgColor).replaceAll("{{bgColor}}", params.bgColor).replaceAll("{{spacing}}", params.spacing);
          }
        },
        "Triangles": {
          draw: (params) => {
            const cssBackground = "conic-gradient(from 26.56505deg, {{bgColor}} 0% 63.43495deg, {{fgColor}} 0% 126.8699deg, {{bgColor}} 0% 50%, {{fgColor}} 0% 243.43495deg, {{bgColor}} 0% 306.8699deg, {{fgColor}} 0%) 0 0/ calc(9/16*{{spacing}}vh) {{spacing}}vh";
            elePattern.style.background = cssBackground.replaceAll("{{fgColor}}", params.fgColor).replaceAll("{{bgColor}}", params.bgColor).replaceAll("{{spacing}}", params.spacing);
          }
        }
      };
      /**
       * initialize Singular widget object and define callback functions
       */
      SingularWidget.init({
        onInit: onSingularInit,
        onValue: onSingularValue
      });
      /**
       * addEventListener for window resizing
       * relevant when your widget displays HTML content or uses widget compositions
       */
      window.addEventListener("resize", function() {
        if (windowWidth != window.innerWidth || windowHeight != window.innerHeight) {
          windowWidth = window.innerWidth;
          windowHeight = window.innerHeight;
        }
      });
      /**
       * onSingularInit()
       * called when the widget instance is created
       */
      function onSingularInit(params) {
        console.log("onSingularInit() - SingularWidget =", SingularWidget);
        console.log("onSingularInit() - params =", params);
        windowWidth = window.innerWidth;
        windowHeight = window.innerHeight;
        elePattern = document.getElementById('pattern');
      }
      /**
       * onSingularValue()
       * called when the widget instance is created or the instance data is changed
       */
      function onSingularValue(json) {
        console.log("onSingularValue() - json =", json);
        patternChanged = false;
        if (json.patternId !== undefined && json.patternId != pattern) {
          pattern = json.patternId;
          patternChanged = true;
        }
        if (json.spacingId !== undefined && Number(json.spacingId) != spacing) {
          spacing = Number(json.spacingId);
          patternChanged = true;
        }
        const newBgColor = tinycolor(json.backColorId).toHex8String();
        if (json.backColorId !== undefined && newBgColor != bgColor) {
          bgColor = newBgColor;
          patternChanged = true;
        }
        const newFgColor = tinycolor(json.frontColorId).toHex8String();
        if (json.frontColorId !== undefined && newFgColor != fgColor) {
          fgColor = newFgColor;
          patternChanged = true;
        }
        if (patternChanged === true) {
          redrawPattern({
            spacing: spacing,
            bgColor: bgColor,
            fgColor: fgColor
          });
        }
      }
      /**
       * redrawPattern()
       */
      const redrawPattern = function(params) {
        // we reset all style properties
        elePattern.style.background = "";
        elePattern.style.backgroundImage = "";
        elePattern.style.backgroundPosition = "";
        elePattern.style.backgroundSize = "";
        elePattern.style.backgroundBlendMode = "";
        // draw pattern
        patternStyles[pattern].draw(params);
      }
    </script>
  </body>
</html>

Widget UI
Pattern Widget UI in Composer