├── simplecountdown ├── images │ └── logo.png ├── css │ ├── countdown.css │ └── countdown-settings.css ├── description.xml ├── countdown.html ├── countdown-settings.html └── scripts │ ├── countdown.js │ └── countdown-settings.js ├── TwitchAuthTest ├── css │ ├── auth.css │ └── auth-settings.css ├── description.xml ├── auth.html ├── auth-settings.html └── scripts │ ├── auth.js │ └── auth-settings.js └── README.md /simplecountdown/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Telestream/GameshowWidgetSDK/HEAD/simplecountdown/images/logo.png -------------------------------------------------------------------------------- /TwitchAuthTest/css/auth.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | background-color: white; 4 | margin: 0px; 5 | padding: 0px; 6 | border: 0px; 7 | } 8 | -------------------------------------------------------------------------------- /simplecountdown/css/countdown.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | background-color: rgba(127,127,127,127); 4 | color: white; 5 | font-size: 100px; 6 | font-weight: bold; 7 | font-family: sans-serif; 8 | } 9 | -------------------------------------------------------------------------------- /simplecountdown/description.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | -------------------------------------------------------------------------------- /TwitchAuthTest/description.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | -------------------------------------------------------------------------------- /simplecountdown/countdown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Countdown 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /TwitchAuthTest/auth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OAuth test 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
User Name
18 | 19 |
20 |
E-mail
23 | 24 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /simplecountdown/countdown-settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Countdown - settings 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
Start
 seconds
20 | 21 |
22 |
Stop
 seconds
30 | 31 |
32 |
Delay
 seconds
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /simplecountdown/scripts/countdown.js: -------------------------------------------------------------------------------- 1 | var gWidgetSettings = { 2 | startSeconds: "10", 3 | stopSeconds: "0", 4 | delaySeconds: 1 5 | }; 6 | 7 | // This will also be called by the host app *Gameshow* to pass widget configuration 8 | // parameters from widget settings component to the renderer component (this component). 9 | function SetWidgetSettings(widgetSettings, appInfo) 10 | { 11 | gWidgetSettings = widgetSettings; 12 | currentSeconds = gWidgetSettings.startSeconds; 13 | } 14 | 15 | function updateCountdown() 16 | { 17 | if (currentSeconds == gWidgetSettings.stopSeconds) 18 | { 19 | document.getElementById("countdown").style.display = 'none'; 20 | document.getElementById("countdown-succeeded").style.display = 'inline'; 21 | 22 | currentSeconds = gWidgetSettings.startSeconds; 23 | } 24 | else 25 | { 26 | document.getElementById("countdown").innerHTML = currentSeconds; 27 | document.getElementById("countdown").style.display = 'inline'; 28 | document.getElementById("countdown-succeeded").style.display = 'none'; 29 | 30 | currentSeconds--; 31 | } 32 | 33 | setTimeout(updateCountdown, 1000 * gWidgetSettings.delaySeconds); 34 | } 35 | 36 | window.onload = function() { 37 | 38 | currentSeconds = gWidgetSettings.startSeconds; 39 | 40 | // use setTimeout instead of setInterval so we can adjust delay on the fly 41 | setTimeout(updateCountdown, 1000 * gWidgetSettings.delaySeconds); 42 | } 43 | -------------------------------------------------------------------------------- /TwitchAuthTest/css/auth-settings.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | } 4 | 5 | .settings-row { 6 | height: 23px; 7 | margin-top: 0.6em; 8 | margin-bottom: 0.6em; 9 | } 10 | 11 | .setting-name { 12 | text-align: right; 13 | width: 7em; 14 | height: auto; 15 | display: inline-block; 16 | margin-right: 10px; 17 | } 18 | 19 | .setting-name-long { 20 | width: 8em; 21 | } 22 | 23 | .setting-name-short { 24 | width: 3em; 25 | } 26 | 27 | .setting-value { 28 | width: 11em; 29 | height: auto; 30 | } 31 | 32 | .setting-value-short { 33 | width: 7.3em; 34 | height: auto; 35 | } 36 | 37 | .setting-value-shorter { 38 | width: 3.2em; 39 | height: auto; 40 | } 41 | 42 | .vpad-small { 43 | padding-top: 2px; 44 | padding-bottom: 2px; 45 | } 46 | 47 | .no-padding { 48 | padding: 0px; 49 | } 50 | 51 | .no-border { 52 | border: 0px; 53 | } 54 | 55 | .no-margin { 56 | margin: 0px; 57 | } 58 | 59 | .valign-center { 60 | vertical-align: middle; 61 | } 62 | 63 | .halign-right { 64 | float: right; 65 | } 66 | 67 | .logo { 68 | width: 1.6em; 69 | } 70 | 71 | .inline { 72 | display: inline-block; 73 | } 74 | 75 | .gap { 76 | display: inline-block; 77 | width: 0.5em; 78 | } 79 | 80 | .setting-value-frame { 81 | border-width: 1px; 82 | border-style: solid; 83 | border-color: #747579; 84 | -webkit-border-radius: 4px; 85 | border-radius: 4px; 86 | padding: 2px; 87 | } 88 | -------------------------------------------------------------------------------- /simplecountdown/css/countdown-settings.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | } 4 | 5 | .settings-row { 6 | height: 23px; 7 | margin-top: 0.6em; 8 | margin-bottom: 0.6em; 9 | } 10 | 11 | .setting-name { 12 | text-align: right; 13 | width: 7em; 14 | height: auto; 15 | display: inline-block; 16 | margin-right: 10px; 17 | } 18 | 19 | .setting-name-long { 20 | width: 8em; 21 | } 22 | 23 | .setting-name-short { 24 | width: 3em; 25 | } 26 | 27 | .setting-value { 28 | width: 11em; 29 | height: auto; 30 | } 31 | 32 | .setting-value-short { 33 | width: 7.3em; 34 | height: auto; 35 | } 36 | 37 | .setting-value-shorter { 38 | width: 3.2em; 39 | height: auto; 40 | } 41 | 42 | .vpad-small { 43 | padding-top: 2px; 44 | padding-bottom: 2px; 45 | } 46 | 47 | .no-padding { 48 | padding: 0px; 49 | } 50 | 51 | .no-border { 52 | border: 0px; 53 | } 54 | 55 | .no-margin { 56 | margin: 0px; 57 | } 58 | 59 | .valign-center { 60 | vertical-align: middle; 61 | } 62 | 63 | .halign-right { 64 | float: right; 65 | } 66 | 67 | .logo { 68 | width: 1.6em; 69 | } 70 | 71 | .inline { 72 | display: inline-block; 73 | } 74 | 75 | .gap { 76 | display: inline-block; 77 | width: 0.5em; 78 | } 79 | 80 | .setting-value-frame { 81 | border-width: 1px; 82 | border-style: solid; 83 | border-color: #747579; 84 | -webkit-border-radius: 4px; 85 | border-radius: 4px; 86 | padding: 2px; 87 | } 88 | -------------------------------------------------------------------------------- /TwitchAuthTest/auth-settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OAuth test - settings 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
User Name
21 | 22 |
23 |
E-mail
28 | 29 |
30 |
Avatar
35 | 36 |
37 | 38 |
39 |
Access Token
NONE
42 | 43 |
44 |
Channel Name
NONE
47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /simplecountdown/scripts/countdown-settings.js: -------------------------------------------------------------------------------- 1 | var gWidgetSettings = { 2 | startSeconds: "10", 3 | stopSeconds: "0", 4 | delaySeconds: 1, 5 | _template_exclude: [] 6 | }; 7 | 8 | var gAppInfo = null; 9 | 10 | function settings_ui_update_controls() 11 | { 12 | document.getElementById("start_seconds").value = gWidgetSettings.startSeconds; 13 | document.getElementById("stop_seconds").value = gWidgetSettings.stopSeconds; 14 | document.getElementById("delay_seconds").value = gWidgetSettings.delaySeconds; 15 | } 16 | 17 | function save_settings() 18 | { 19 | gWidgetSettings.startSeconds = document.getElementById("start_seconds").value; 20 | gWidgetSettings.stopSeconds = document.getElementById("stop_seconds").value; 21 | gWidgetSettings.delaySeconds = document.getElementById("delay_seconds").value; 22 | } 23 | 24 | function push_settings_to_renderer() 25 | { 26 | if (window.hostApp) 27 | { 28 | gWidgetSettings._runtime = { 29 | geometry: { 30 | width: document.documentElement.scrollWidth, 31 | height: document.documentElement.scrollHeight 32 | } 33 | }; 34 | 35 | window.hostApp.execute('apply_widget_settings', JSON.stringify(gWidgetSettings)); 36 | } 37 | } 38 | 39 | // Call this to push setting changes to the host app *Gameshow* so it would 40 | // update the rendering component with latest configuration parameters 41 | function SaveSettingsAndNotifyHostApp() 42 | { 43 | save_settings(); 44 | push_settings_to_renderer(); 45 | } 46 | 47 | // called by Gameshow to retrieve settings. 48 | // settings can be any javascript object, which Gameshow will serialize as JSON 49 | // and save in document so it can persist between application sessions 50 | function GetWidgetSettings() { 51 | return gWidgetSettings; 52 | } 53 | 54 | // This will be called by the host app *Gameshow* to restore setting values. 55 | // appInfo may include oauth_access_token and twitch_channel_name, if the host 56 | // app *Gameshow* was authorized by the user to access Twitch. 57 | function SetWidgetSettings(widgetSettings, appInfo) 58 | { 59 | if (widgetSettings != null) 60 | { 61 | gWidgetSettings = widgetSettings; 62 | } 63 | 64 | if (appInfo != null) 65 | { 66 | gAppInfo = appInfo; 67 | } 68 | 69 | settings_ui_update_controls(); 70 | push_settings_to_renderer(); 71 | } 72 | -------------------------------------------------------------------------------- /TwitchAuthTest/scripts/auth.js: -------------------------------------------------------------------------------- 1 | var gWidgetSettings = { 2 | showImage: true, 3 | showDisplayName: true, 4 | showEmail: true 5 | }; 6 | 7 | var gAppInfo = null; 8 | 9 | function renderer_update_view() 10 | { 11 | if (gWidgetSettings != null) 12 | { 13 | if (gWidgetSettings.showDisplayName) 14 | { 15 | $("#username_group").show(); 16 | } 17 | else 18 | { 19 | $("#username_group").hide(); 20 | } 21 | 22 | if (gWidgetSettings.showImage) 23 | { 24 | $("#twitch_image_group").show(); 25 | } 26 | else 27 | { 28 | $("#twitch_image_group").hide(); 29 | } 30 | 31 | if (gWidgetSettings.showEmail) 32 | { 33 | $("#email_group").show(); 34 | } 35 | else 36 | { 37 | $("#email_group").hide(); 38 | } 39 | } 40 | 41 | if (gAppInfo != null && gAppInfo.oauth_access_token != null) 42 | { 43 | var accessToken = gAppInfo.oauth_access_token; 44 | 45 | // described at https://github.com/justintv/Twitch-API/blob/master/v3_resources/users.md 46 | // authenticated and requires scope user_read 47 | $.ajax({ 48 | url: "https://api.twitch.tv/kraken/user", 49 | type: "GET", 50 | dataType: "json", 51 | headers: 52 | { 53 | "Accept": "application/vnd.twitchtv.v3+json", 54 | "Authorization": "OAuth "+accessToken 55 | } 56 | }).done(function (json) { 57 | document.getElementById("username").innerText = json.display_name; 58 | document.getElementById("email").innerText = json.email; 59 | document.getElementById("twitch_image").alt = "avatar"; 60 | document.getElementById("twitch_image").src = json.logo; 61 | }) 62 | } 63 | else 64 | { 65 | document.getElementById("username").innerText = "NOT AUTHENTICATED" 66 | document.getElementById("email").innerText = "NOT AUTHENTICATED" 67 | document.getElementById("twitch_image").alt = "NOT AUTHENTICATED"; 68 | document.getElementById("twitch_image").src = ""; 69 | } 70 | } 71 | 72 | // This will also be called by the host app *Gameshow* to pass widget configuration 73 | // parameters from widget settings component to the renderer component (this component). 74 | function SetWidgetSettings(widgetSettings, appInfo) 75 | { 76 | if (widgetSettings != null) 77 | { 78 | gWidgetSettings = widgetSettings; 79 | } 80 | 81 | if (appInfo != null) 82 | { 83 | gAppInfo = appInfo; 84 | } 85 | 86 | renderer_update_view(); 87 | } 88 | -------------------------------------------------------------------------------- /TwitchAuthTest/scripts/auth-settings.js: -------------------------------------------------------------------------------- 1 | var gWidgetSettings = { 2 | showImage: true, 3 | showDisplayName: true, 4 | showEmail: true 5 | }; 6 | 7 | var gAppInfo = null; 8 | 9 | function settings_ui_update_controls() 10 | { 11 | if (gWidgetSettings != null) 12 | { 13 | $('#show_image').prop('checked', gWidgetSettings.showImage); 14 | $('#show_display_name').prop('checked', gWidgetSettings.showDisplayName); 15 | $('#show_email').prop('checked', gWidgetSettings.showEmail); 16 | } 17 | 18 | if (gAppInfo != null && gAppInfo.oauth_access_token != null) 19 | { 20 | document.getElementById("access_token").innerText = gAppInfo.oauth_access_token; 21 | document.getElementById("channel_name").innerText = gAppInfo.twitch_channel_name; 22 | } 23 | else 24 | { 25 | document.getElementById("access_token").innerText = "UNAUTHENTICATED"; 26 | document.getElementById("channel_name").innerText = "UNKNOWN"; 27 | } 28 | } 29 | 30 | function save_settings() 31 | { 32 | gWidgetSettings.showImage = $('#show_image').is(":checked"); 33 | gWidgetSettings.showDisplayName = $('#show_display_name').is(":checked"); 34 | gWidgetSettings.showEmail = $('#show_email').is(":checked"); 35 | } 36 | 37 | function push_settings_to_renderer() 38 | { 39 | if (window.hostApp) 40 | { 41 | gWidgetSettings._runtime = { 42 | geometry: { 43 | width: document.documentElement.scrollWidth, 44 | height: document.documentElement.scrollHeight 45 | } 46 | }; 47 | 48 | window.hostApp.execute('apply_widget_settings', JSON.stringify(gWidgetSettings)); 49 | } 50 | } 51 | 52 | // Call this to push setting changes to the host app *Gameshow* so it would 53 | // update the rendering component with latest configuration parameters 54 | function SaveSettingsAndNotifyHostApp() 55 | { 56 | save_settings(); 57 | push_settings_to_renderer(); 58 | } 59 | 60 | // called by Gameshow to retrieve settings. 61 | // settings can be any javascript object, which Gameshow will serialize as JSON 62 | // and save in document so it can persist between application sessions 63 | function GetWidgetSettings() { 64 | return gWidgetSettings; 65 | } 66 | 67 | 68 | // This will be called by the host app *Gameshow* to restore setting values. 69 | // appInfo may include oauth_access_token and twitch_channel_name, if the host 70 | // app *Gameshow* was authorized by the user to access Twitch. 71 | function SetWidgetSettings(widgetSettings, appInfo) 72 | { 73 | if (widgetSettings != null) 74 | { 75 | gWidgetSettings = widgetSettings; 76 | } 77 | 78 | if (appInfo != null) 79 | { 80 | gAppInfo = appInfo; 81 | } 82 | 83 | settings_ui_update_controls(); 84 | push_settings_to_renderer(); 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gameshow Widget Plug-ins 2 | *Gameshow* presentation can be extended with widget plug-ins implemented using common web development technologies. The plug-in widget rendering is carried out by *Gameshow* via *Chromium Embedded Framework* on Mac and Windows. The widget settings UI is presented via *WebKit* on Macs, and *CEF* on Windows. Widget plug-in developers are assumed to be familiar with XML, HTML, Javascript, and JSON. 3 | 4 | ## Widget plug-in discovery 5 | *Gameshow* will load widget plug-ins from the following locations on Windows: 6 | - `C:\Program Files\Telestream\Gameshow\eva_plugins\Sources\WidgetSDK` for built-in widgets that ship with *Gameshow*. 7 | - `C:\Users\Public\Documents\Gameshow\WidgetSDK` for user installed third party widgets. 8 | 9 | *Gameshow* will load widget plug-ins from the following locations on Macs: 10 | - `Gameshow.app/Contents/PlugIns/Sources/WidgetSDK` for built-in widgets that ship with *Gameshow*. 11 | - `~/Library/Application Support/Gameshow/WidgetSDK` for user installed third party widgets. 12 | 13 | ## Widget plug-in folder anatomy 14 | Below is an example of a typical folder layout for a widget plug-in. 15 | ``` 16 | simplecountdown 17 | ├── countdown-settings.html 18 | ├── countdown.html 19 | ├── css 20 | │ ├── countdown-settings.css 21 | │ └── countdown.css 22 | ├── description.xml 23 | ├── images 24 | │ └── logo.png 25 | └── scripts 26 | ├── countdown-settings.js 27 | └── countdown.js 28 | ``` 29 | 30 | Gameshow expects to find the plug-in manifest inside the plug-in folder, and it *must* be named `description.xml`. The contents of this particular manifest are shown below. 31 | 32 | ``` 33 | 34 | 46 | 47 | ``` 48 | 49 | * `name` attribute specifies a plug-in ID. It should be unique. 50 | * `display_name` attribute specifies a widget name that will be displayed in *Gameshow*. 51 | * `version` attribute specifies release version of the plug-in. This is required. This should be numerical only and contain up to 3 fields (`major[.minor[.patch]]`). For example, `1.0.0`. 52 | * `creator` attribute identifies the organization or authors of the plug-in. This is required. 53 | * `authentication_type` is an optional attribute that may be used to group widgets together in a sub-menu. Currently only *"twitch"* is recognized. 54 | * `start_page` attribute specifies the main widget rendering component. This is the part of the widget that is rendered and composited into the shot. Default value is `index.html`. 55 | * `min_width` and `min_height` attributes specify the minimum resolution of the rendering component. Default value is `640` for `min_width` and `480` for `min_height`. 56 | * `settings_page` is an optional attribute that specifies the widget settings component. This part of the widget is presented to the user in the *Shot Properties Inspector* / *Source Settings* page. Whether the widget requires any configuration settings is entirely up to the widget authors. Default value is `settings.html`. 57 | * `settings_height` is an optional attribute that specifies the suggested minimum height of the Gameshow container widget used to host the plug-in settings UI. Default value is `480`. The widget may change its height requirements dynamically via `_runtime` parameter as discussed in *Interaction between widget plug-in and Gameshow*. 58 | * `provides_renderer_geometry` is an optional attribute indicating whether widget rendering component can calculate its own size, allowing renderer `width` and `height` to be unconstrained in *Gameshow* and to change dynamically at runtime. This is discussed in more detail in *Interaction between widget plug-in and Gameshow*. 59 | * `api_version` attribute specifies the version of the host API. This is required and should always be set to `1`. 60 | 61 | While some of the plug-in folder layout is specified in the manifest, the settings UI and widget rendering components may reference additional files. In the example given here `countdown-settings.html` references two other files included in the plug-in folder, as shown below. 62 | ``` 63 | 64 | 65 | ``` 66 | 67 | The widget rendering component may reference some of the same and additional files in the plug-in folder. Here *Twitch OAuth Test* settings stylesheet is referenced by the rendering component for code re-use. 68 | ``` 69 | 70 | 71 | 72 | ``` 73 | 74 | Additional assets (images, jQuery, etc...) may be included in the plug-in folder; this is left entirely up to the plug-in authors. 75 | 76 | ## Interaction between widget plug-in and Gameshow 77 | *Gameshow* keeps independent instances of widget components -- the settings UI component, and the rendering component. These components are executed in separate embedded browser instances. *Gameshow* receives configuration parameters from the settings component and serializes them to JSON. Serialized parameters are deserialized from JSON and passed to the rendering component. The settings component and rendering component never communicate directly within *Gameshow*. 78 | 79 | Below is an example of the *Countdown* widget settings. 80 | ``` 81 | { 82 | "startSeconds": "11", 83 | "stopSeconds": "0", 84 | "delaySeconds": "1", 85 | "_runtime": { 86 | "geometry": { 87 | "width": 1430, 88 | "height": 107 89 | } 90 | } 91 | } 92 | ``` 93 | 94 | In the listing above all keys (except `_runtime`) are rendering component configuration parameters, and their interpretation is left entirely up to the widget author. 95 | 96 | The purpose of the `_runtime` parameter is to notify *Gameshow* about the minimum container height required to hold the widget settings UI. While all other parameters are merely passed along to the rendering component, the `_runtime` parameter is interpreted by *Gameshow*. The `_runtime` parameter may be supplied by widget author using the following snippet of code. 97 | ``` 98 | settings._runtime = { 99 | geometry: { 100 | width: document.documentElement.scrollWidth, 101 | height: document.documentElement.scrollHeight 102 | } 103 | }; 104 | ``` 105 | 106 | *Gameshow* must be notified whenever the user changes a setting value. This can be accomplished via the snippet of code shown below. 107 | ``` 108 | if (window.hostApp) { 109 | window.hostApp.execute('apply_widget_settings', JSON.stringify(settings)); 110 | } 111 | ``` 112 | 113 | *Gameshow* will call `SetWidgetSettings(settings, applicationInfo)` for both the settings component and the rendering component. For the settings component it is called to (re)initialize the settings UI with the previously saved values. For the rendering component it is called to pass widget settings to the rendering component. 114 | 115 | *Gameshow* will call `GetWidgetSettings()` for the rendering component, and may call it for the settings component. For the rendering component it will be called when exporting a document template. Document templates should not include any sensitive widget settings (passwords, access keys, fragile URLs, etc...) -- these should be listed in `settings._template_exclude` array. Below is a listing of a sample settings object that contains a couple of settings (`text_filepath`, and `_private.access_key`) that should not be part of the exported template. 116 | ``` 117 | { 118 | "text_source": "FILE", 119 | "text": "dGhpcyVDMiVBMGlzJUMyJUEwKG5vdCVDMiVBMGxpdmUpJUMyJUEwdGV4dCUyQyVDMiVBMG9uZSVDMiVBMGxpbmVyJTBBJTBBJTBBNHRoJUMyJUEwbGluZSUwQQ==", 120 | "text_filepath": "file:///Some/Non/Portable/Path/To/current-date-time.txt", 121 | "_private": { "access_key": "SomeSecretKey" }, 122 | "_template_exclude": [ 123 | "text_filepath", 124 | "_private.access_key" 125 | ] 126 | } 127 | ``` 128 | Note that if `settings._template_exclude` is omitted then none of the widget settings will be included in the exported template. Similarly, if `settings._template_exclude` is present and its value is an empty array (`settings._template_exclude = [];`) then all of the widget settings will be included in the exported template. 129 | 130 | *Gameshow* will supply OAuth access tokens for Twitch or YouTube (depending on which service the user is logged in to) in the `applicationInfo` parameter passed to `SetWidgetSettings(settings, applicationInfo)`. Below is a listing of a sample value of `applicationInfo`. 131 | ``` 132 | { 133 | "twitch_channel_name": "test_channel", 134 | "oauth_access_token": "some_auth_token" 135 | } 136 | ``` 137 | 138 | ### Widgets with content-defined size 139 | *Gameshow* can accommodate plug-in widgets with an implicitly defined width and height. An example of this would be a text rendering widget where the size of the rendering component depends on the font settings, the text being rendered, whether the width of the rendering component is unconstrained or fixed, and whether line wrapping is enabled. A widget can indicate to *Gameshow* that it can calculate the size of its rendering component by adding `provides_renderer_geometry="true"` to `description.xml`. A widget that supports implicit sizing will need to examine `settings._renderer` record to see how its geometry has been constrained (`width_constrained`, `height_constrained`, `client_width`, `client_height`). When `width` is constrained the *Gameshow* widget should reference `_renderer.client_width` for the expected renderer width value. Note that `width_constrained`, `height_constrained`, `client_width` and `client_height` are values supplied by *Gameshow* to inform the widget about the intended layout behavior. *Gameshow* will ignore these values when it receives widget settings. The widget is expected to provide its unconstrained implicit `width` and `height` in `_renderer.geometry` record when it submits settings to *Gameshow*. Also required is `_renderer.constrained_width_geometry` which should provide the `height` of the renderer component required to accommodate (possibly line-wrapped) content within the `_renderer.client_width` constraint. Below is an excerpt of the `settings._renderer` record for the Text widget that ships with *Gameshow*. 140 | ``` 141 | { 142 | "_renderer": { 143 | "width_constrained": false, 144 | "height_constrained": false, 145 | "client_width": 1920, 146 | "client_height": 1080, 147 | "geometry": { 148 | "width": 1731, 149 | "height": 672 150 | }, 151 | "constrained_width_geometry": { 152 | "width": 1731, 153 | "height": 672 154 | } 155 | } 156 | } 157 | ``` 158 | 159 | #### File selection from a widget plug-in 160 | *Gameshow* hostApp API allows plug-ins to prompt the user for file selection and to receive the selection result as a list of `file://` URLs. Below is an example of single-file selection of arbitrary files: 161 | ``` 162 | obj.selectTextSourceFile = function () { 163 | if (window.hostApp) { 164 | var options = { allowsMultipleSelection: false }; 165 | window.hostApp.execute('openFileDialog', JSON.stringify(options)); 166 | } 167 | }; 168 | ``` 169 | 170 | Selection result is delivered via a call to `hostAppCallback(commandName, commandResult)`, where `commandName` is set to `'receiveSelectedFileURLs'` and `commandResult` includes stringified JSON array of selected file URLs. 171 | ``` 172 | obj.hostAppCallbackHandlers = { 173 | receiveSelectedFileURLs: function (commandResult) { 174 | var files = JSON.parse(commandResult); 175 | if (files.length > 0) { 176 | var settings = obj.currentSettings(); 177 | settings.text_filepath = files[0]; 178 | } 179 | } 180 | }; 181 | 182 | /* . . . */ 183 | 184 | function hostAppCallback(commandName, commandResult) { 185 | var ws = WidgetSettingsSingleton(); 186 | var callbackHandler = ws.hostAppCallbackHandlers[commandName]; 187 | if (callbackHandler) { 188 | callbackHandler(commandResult); 189 | } 190 | } 191 | ``` 192 | 193 | Note that *Gameshow* hostApp `openFileDialog` command supports several configuration options: 194 | - `allowsMultipleSelection` option can be set to `true` or `false`. 195 | - `initialDirectory` option can be set to `'AUDIO'`, `'IMAGE'`, `'VIDEO'`, `'HOME'`, `'DESKTOP'`, `'file://Some/File/Path...'`. 196 | - `fileTypeClass` option can be set to `'AUDIO_FILETYPE'`, `'IMAGE_FILETYPE'`, `'VIDEO_FILETYPE'`, `'MEDIA_FILETYPE'`. `'MEDIA_FILETYPE'` implies selection of audio, video, and image files. 197 | - `fileTypes` option can be used to specify a list of eligible file extensions, for example `'.jpg;.png'`. Note that `fileTypeClass` and `fileTypes` are mutually exclusive. 198 | 199 | 200 | ## Plug-in widget look and feel within Gameshow 201 | *Gameshow* tries to maintain a consistent look and feel between plug-in widget settings controls and the rest of the *Gameshow* UI. This is accomplished by injecting a stylesheet into the embedded browser instance hosting the widget settings component, and the rendering component. The CSS injected into the settings component instance is different depending on the area of the UI where the widget settings are being presented -- the *Shot Properties Inspector* panel uses dark backgrounds and is more customized, while the *Source Settings* dialog maintains native look and feel. 202 | 203 | On Macs these stylesheets can be found at the following locations: 204 | - `Gameshow.app/Contents/Resources/widget-preview-settings.css` for properties inspector panel. 205 | - `Gameshow.app/Contents/Resources/widget-mac-settings.css` for source settings dialog. 206 | 207 | On Windows the stylesheets can be found here: 208 | - `C:\Program Files\Telestream\Gameshow\rsrc\widget-preview-settings.css` for properties inspector panel. 209 | - `C:\Program Files\Telestream\Gameshow\rsrc\widget-windows-settings.css` for source settings dialog. 210 | 211 | These files may be referenced during plug-in widget development for testing purposes only, and should not be referenced directly by the widget in the final deployed plug-in. 212 | 213 | The built-in widgets that are shipped with *Gameshow* maintain consistent look and feel of the various controls by following a few simple conventions outlined below. 214 | - The controls are laid out in a form as a set of rows. 215 | - Each row lays out setting label on the left (right-justified) and value on the right (left-justified). 216 | - The width of the setting label column is typically `7em`. 217 | - The width of the setting value column is `11em` for settings that need more space, `7.3em` for shorter setting values, and `3.2em` for really small setting values. 218 | - The gap between setting label and setting value is `0.5em`. 219 | 220 | These conventions are embodied in the common stylesheet referenced by the built-in *Gameshow* widget plug-ins. You can find this stylesheet at `Gameshow.app/Contents/PlugIns/Sources/WidgetSDK/common/css/settings.css` on Macs, and `C:\Program Files\Telestream\Gameshow\eva_plugins\Sources\WidgetSDK\common\css\settings.css` on Windows. 221 | 222 | Additionally, built-in widgets use a common set of controls. In particular this applies to the color selection functionality provided by [https://github.com/PitPik/tinyColorPicker](https://github.com/PitPik/tinyColorPicker). In order to maintain consistent look and feel, plug-in developers are encouraged to choose the same controls as used by the built-in plug-ins whenever there are multiple choices of controls available. 223 | 224 | 225 | 226 | --------------------------------------------------------------------------------