diff --git a/.gitignore b/.gitignore index 2daf55d..bab3ab2 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ site/ Thumbs.db *.lscache /plugin-out +/.cr diff --git a/CHANGELOG.md b/CHANGELOG.md index 72438c9..dd30c46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,17 @@ Notes: --- +## v1.2.3.0 - Minor Release + +### Features / Improvements + +- Added a `Touch Strip Text` toggle for `Adaptive Dial` so the dial title can be shown or hidden on the Stream Deck+ touch strip. + +### Bug Fixes + +- Reworked the Adaptive Dial feedback layouts to remove the default grey bottom-right bar. +- Increased the usable icon area and balanced the captioned layout so the icon no longer looks cramped next to the touch-strip text. + ## v1.2.2.1 - Build Release ### Internal / Refactor diff --git a/PluginCore/ActionKeys/SCDialActionBase.cs b/PluginCore/ActionKeys/SCDialActionBase.cs index 0249fb7..9589f50 100644 --- a/PluginCore/ActionKeys/SCDialActionBase.cs +++ b/PluginCore/ActionKeys/SCDialActionBase.cs @@ -21,6 +21,13 @@ namespace SCStreamDeck.ActionKeys; /// public abstract class SCDialActionBase : EncoderBase { + private const string DefaultDialTitle = "Adaptive Dial"; + private const string DialLayoutNoCaption = "Layouts/adaptiveDial.json"; + private const string DialLayoutWithCaption = "Layouts/adaptiveDial.caption.json"; + + private string _customDialTitle = DefaultDialTitle; + private string? _activeDialLayout; + [ExcludeFromCodeCoverage] protected SCDialActionBase(SDConnection connection, InitialPayload payload) : base(connection, payload) { @@ -39,6 +46,7 @@ protected SCDialActionBase(SDConnection connection, InitialPayload payload) : ba InitializationService.KeybindingsStateChanged += OnKeybindingsStateChanged; PluginLocaleService.LocaleChanged += OnPluginLocaleChanged; Connection.OnPropertyInspectorDidAppear += OnPropertyInspectorDidAppear; + Connection.OnTitleParametersDidChange += OnTitleParametersDidChange; Connection.OnSendToPlugin += OnSendToPlugin; TryMigrateFunctionSettingsIfPossible(); @@ -47,6 +55,8 @@ protected SCDialActionBase(SDConnection connection, InitialPayload payload) : ba { SendPropertyInspectorUpdate(); } + + SendDialTextUpdate(); } private InitializationService InitializationService { get; } @@ -99,6 +109,7 @@ public override void OnTick() public override void Dispose() { Connection.OnPropertyInspectorDidAppear -= OnPropertyInspectorDidAppear; + Connection.OnTitleParametersDidChange -= OnTitleParametersDidChange; Connection.OnSendToPlugin -= OnSendToPlugin; InitializationService.KeybindingsStateChanged -= OnKeybindingsStateChanged; PluginLocaleService.LocaleChanged -= OnPluginLocaleChanged; @@ -116,10 +127,44 @@ public override void ReceivedSettings(ReceivedSettingsPayload payload) } TryMigrateFunctionSettingsIfPossible(); + SendDialTextUpdate(); } private void SendPropertyInspectorUpdate() => _ = SendPropertyInspectorUpdateAsync(); + private void SendDialTextUpdate() => _ = UpdateDialTextAsync(); + + private async Task UpdateDialTextAsync() + { + try + { + bool showText = Settings.ShowTouchText; + string targetLayout = showText ? DialLayoutWithCaption : DialLayoutNoCaption; + + if (!string.Equals(_activeDialLayout, targetLayout, StringComparison.Ordinal)) + { + await Connection.SetFeedbackLayoutAsync(targetLayout).ConfigureAwait(false); + _activeDialLayout = targetLayout; + } + + string caption = showText ? BuildTouchStripText() : string.Empty; + + await Connection.SetFeedbackAsync(new JObject + { + ["caption"] = caption + }).ConfigureAwait(false); + } + catch (Exception ex) + { + Log.Err($"[{GetType().Name}] Failed to update dial text: {ex.Message}", ex); + } + } + + private string BuildTouchStripText() + { + return string.IsNullOrWhiteSpace(_customDialTitle) ? DefaultDialTitle : _customDialTitle; + } + private async Task SendPropertyInspectorUpdateAsync() { PluginLocaleResolution pluginLocale = await GetPluginLocaleResolutionAsync().ConfigureAwait(false); @@ -171,10 +216,28 @@ private async Task GetPluginLocaleResolutionAsync() private void OnPropertyInspectorDidAppear(object? sender, SDEventReceivedEventArgs e) => SendPropertyInspectorUpdate(); + private void OnTitleParametersDidChange(object? sender, SDEventReceivedEventArgs e) + { + try + { + string? incomingTitle = e.Event?.Payload?.Title; + _customDialTitle = string.IsNullOrWhiteSpace(incomingTitle) + ? DefaultDialTitle + : incomingTitle.Trim(); + + SendDialTextUpdate(); + } + catch (Exception ex) + { + Log.Err($"[{GetType().Name}] Failed to process title update: {ex.Message}", ex); + } + } + private void OnKeybindingsStateChanged() { TryMigrateFunctionSettingsIfPossible(); SendPropertyInspectorUpdate(); + SendDialTextUpdate(); } private void OnPluginLocaleChanged() => SendPropertyInspectorUpdate(); diff --git a/PluginCore/ActionKeys/Settings/DialSettings.cs b/PluginCore/ActionKeys/Settings/DialSettings.cs index 2bdc6e4..8965a12 100644 --- a/PluginCore/ActionKeys/Settings/DialSettings.cs +++ b/PluginCore/ActionKeys/Settings/DialSettings.cs @@ -18,4 +18,7 @@ public sealed class DialSettings [JsonProperty(PropertyName = "clickSoundPath")] public string? ClickSoundPath { get; set; } + + [JsonProperty(PropertyName = "showTouchText")] + public bool ShowTouchText { get; set; } = false; } diff --git a/PluginCore/Layouts/adaptiveDial.caption.json b/PluginCore/Layouts/adaptiveDial.caption.json new file mode 100644 index 0000000..cab857b --- /dev/null +++ b/PluginCore/Layouts/adaptiveDial.caption.json @@ -0,0 +1,26 @@ +{ + "id": "scsd-adaptive-dial-caption", + "items": [ + { + "key": "background", + "type": "pixmap", + "rect": [0, 0, 200, 100], + "zOrder": 0, + "value": "" + }, + { + "key": "icon", + "type": "pixmap", + "rect": [64, 0, 72, 72], + "zOrder": 1, + "value": "" + }, + { + "key": "caption", + "type": "text", + "rect": [8, 78, 184, 20], + "zOrder": 2, + "value": "" + } + ] +} diff --git a/PluginCore/Layouts/adaptiveDial.json b/PluginCore/Layouts/adaptiveDial.json new file mode 100644 index 0000000..946a5bf --- /dev/null +++ b/PluginCore/Layouts/adaptiveDial.json @@ -0,0 +1,19 @@ +{ + "id": "scsd-adaptive-dial", + "items": [ + { + "key": "background", + "type": "pixmap", + "rect": [0, 0, 200, 100], + "zOrder": 0, + "value": "" + }, + { + "key": "icon", + "type": "pixmap", + "rect": [50, 0, 100, 100], + "zOrder": 1, + "value": "" + } + ] +} diff --git a/PluginCore/PluginCore.csproj b/PluginCore/PluginCore.csproj index eddef10..647af62 100644 --- a/PluginCore/PluginCore.csproj +++ b/PluginCore/PluginCore.csproj @@ -84,6 +84,9 @@ Always + + Always + diff --git a/PluginCore/PropertyInspector/Dials/AdaptiveDial.html b/PluginCore/PropertyInspector/Dials/AdaptiveDial.html index 9a5432f..5f7b667 100644 --- a/PluginCore/PropertyInspector/Dials/AdaptiveDial.html +++ b/PluginCore/PropertyInspector/Dials/AdaptiveDial.html @@ -100,6 +100,17 @@
+ +
+
+
Touch Strip Text
+ +
+
diff --git a/PluginCore/PropertyInspector/js/pi-dial-action.js b/PluginCore/PropertyInspector/js/pi-dial-action.js index d625355..c62bf6c 100644 --- a/PluginCore/PropertyInspector/js/pi-dial-action.js +++ b/PluginCore/PropertyInspector/js/pi-dial-action.js @@ -1,2 +1,2 @@ /* generated: pi-dial-action.js */ -(function(){function o(t){const i=n?.functionPicker?.flattenFunctionsData;return typeof i!="function"?[]:i(t,{requireToggleCandidates:!1,excludeBindingTypes:["mouseaxis","joystick","gamepad"]})}function s(n){r=o(n);t.forEach(n=>{n.dropdown?.setItems?.(r),n.dropdown?.setSelectedValue?.(n.currentValue,{rerender:!1}),n.currentValue&&f(n.settingsKey,n.currentValue)})}function f(n,u){const o=t.find(t=>t.settingsKey===n);if(o){o.currentValue=u||"";const f=r.find(n=>n.value===u||n.legacyValue===u);if(!f){u||i(o,null);return}const s=f.legacyValue===u&&f.value!==u;e(n,f,{persist:s})}}function e(n,r,f={}){const e=t.find(t=>t.settingsKey===n);if(e){const o=f.persist!==!1;u=!0;e.currentValue=r.value;e.dropdown?.setSelectedValue?.(r.value,{rerender:!0});i(e,r);o&&e.setValue(r.value);setTimeout(()=>{u=!1},200)}}function i(t,i){if(t&&t.idPrefix){const u=n?.functionPicker?.updateFunctionDetails;if(typeof u=="function"){const r=t.idPrefix,f=n?.i18n?.t(t.slotLabelKey,t.slotLabelFallback)||t.slotLabelFallback;u({containerEl:document.getElementById(`${r}-details-container`),titleEl:document.getElementById(`${r}-details-title`),descriptionEl:document.getElementById(`${r}-description`),bindingElements:{keyboard:document.getElementById(`${r}-binding-keyboard`),mouse:document.getElementById(`${r}-binding-mouse`),gamepad:document.getElementById(`${r}-binding-gamepad`),joystick:document.getElementById(`${r}-binding-joystick`)},hideContainerWhenEmpty:!0,showContainerWhenFilled:!0,slotLabel:f,selectedOption:i})}}}const n=globalThis.SCPI;n?.i18n?.init?.();n?.i18n?.apply?.();n?.bus?.start?.();n?.theme?.initThemeDropdown?.();let r=[],u=!1;const t=[{rootId:"rotateLeftDropdown",settingsKey:"rotateLeftFunction",slotLabelKey:"PropertyInspector.DialAction.RotateLeftLabel",slotLabelFallback:"Rotate Left",placeholderKey:"PropertyInspector.DialAction.RotateLeftPlaceholder",placeholderFallback:"Select rotate-left function...",idPrefix:"rotateLeft"},{rootId:"rotateRightDropdown",settingsKey:"rotateRightFunction",slotLabelKey:"PropertyInspector.DialAction.RotateRightLabel",slotLabelFallback:"Rotate Right",placeholderKey:"PropertyInspector.DialAction.RotateRightPlaceholder",placeholderFallback:"Select rotate-right function...",idPrefix:"rotateRight"},{rootId:"pressDropdown",settingsKey:"pressFunction",slotLabelKey:"PropertyInspector.DialAction.PushLabel",slotLabelFallback:"Push",placeholderKey:"PropertyInspector.DialAction.PushPlaceholder",placeholderFallback:"Select push function...",idPrefix:"press"}].map(t=>{const i=n?.ui?.dropdown?.initDropdown?.({rootId:t.rootId,placeholder:{key:t.placeholderKey,fallback:t.placeholderFallback},searchEnabled:!0,minLoadingMs:0,successFlashMs:100,emptyText:{key:"PropertyInspector.Common.Dropdown.NoMatchingFunctionsFound",fallback:"No matching functions found"},maxResults:50,getText:n=>String(n?.text??""),getValue:n=>String(n?.value??""),getGroup:n=>String(n?.group??""),isDisabled:n=>!!n?.disabled,onSelect:n=>e(t.settingsKey,n,{persist:!0})});i?.setLoading?.(!0,{key:"PropertyInspector.Common.Status.LoadingFunctions",fallback:"Loading functions"});const[r,o]=globalThis.SDPIComponents.useSettings(t.settingsKey,n=>{u||f(t.settingsKey,n)});return{...t,dropdown:i,getValue:r,setValue:o,currentValue:""}});n?.ui?.filePicker?.createFilePicker?.({rootId:"audioFilePicker",placeholderText:{key:"PropertyInspector.Common.FilePicker.NoFileSelected",fallback:"No file selected"},buttonText:{key:"PropertyInspector.Common.FilePicker.Button",fallback:"FILE"},selectTitle:{key:"PropertyInspector.Common.Audio.SelectTitle",fallback:"Select audio file"},clearTitle:{key:"PropertyInspector.Common.Audio.ClearTitle",fallback:"Clear audio file"},settingsKey:"clickSoundPath"});n?.bus?.on?.(n=>{const r=n?.functionsLoaded;r===!0&&(t.forEach(n=>{n.dropdown?.setLoading?.(!1),document.getElementById(n.rootId)?.classList.remove("pi-dropdown--error")}),s(n.functions||[]));r===!1&&t.forEach(n=>{document.getElementById(n.rootId)?.classList.add("pi-dropdown--error"),n.dropdown?.setLoading?.(!0,{key:"PropertyInspector.Common.Status.NoInstallationDetected",fallback:"No installation detected. Set custom path."}),n.dropdown?.setItems?.([]),i(n,null)})});n?.i18n?.onChange?.(()=>{t.forEach(n=>{const t=r.find(t=>t.value===n.currentValue||t.legacyValue===n.currentValue)||null;i(n,t)})});n?.util?.onDocumentReady?.(()=>{t.forEach(n=>{const t=n.getValue();n.currentValue=t||"";n.dropdown?.setSelectedValue?.(n.currentValue,{rerender:!1});i(n,null)}),n?.bus?.sendOnce?.("pi.connected","propertyInspectorConnected")})})() +(function(){function e(n,t=true){if(typeof n=="boolean")return n;if(typeof n=="string"){const t=n.trim().toLowerCase();if(t==="false"||t==="0"||t==="off"||t==="no")return!1;if(t==="true"||t==="1"||t==="on"||t==="yes")return!0}return t}function l(t){const i=n?.functionPicker?.flattenFunctionsData;return typeof i!="function"?[]:i(t,{requireToggleCandidates:!1,excludeBindingTypes:["mouseaxis","joystick","gamepad"]})}function a(n){u=l(n);t.forEach(n=>{n.dropdown?.setItems?.(u),n.dropdown?.setSelectedValue?.(n.currentValue,{rerender:!1}),n.currentValue&&o(n.settingsKey,n.currentValue)})}function o(n,i){const e=t.find(t=>t.settingsKey===n);if(e){e.currentValue=i||"";const f=u.find(n=>n.value===i||n.legacyValue===i);if(!f){i||r(e,null);return}const o=f.legacyValue===i&&f.value!==i;s(n,f,{persist:o})}}function s(n,i,u={}){const e=t.find(t=>t.settingsKey===n);if(e){const o=u.persist!==!1;f=!0;e.currentValue=i.value;e.dropdown?.setSelectedValue?.(i.value,{rerender:!0});r(e,i);o&&e.setValue(i.value);setTimeout(()=>{f=!1},200)}}function r(t,i){if(t&&t.idPrefix){const u=n?.functionPicker?.updateFunctionDetails;if(typeof u=="function"){const r=t.idPrefix,f=n?.i18n?.t(t.slotLabelKey,t.slotLabelFallback)||t.slotLabelFallback;u({containerEl:document.getElementById(`${r}-details-container`),titleEl:document.getElementById(`${r}-details-title`),descriptionEl:document.getElementById(`${r}-description`),bindingElements:{keyboard:document.getElementById(`${r}-binding-keyboard`),mouse:document.getElementById(`${r}-binding-mouse`),gamepad:document.getElementById(`${r}-binding-gamepad`),joystick:document.getElementById(`${r}-binding-joystick`)},hideContainerWhenEmpty:!0,showContainerWhenFilled:!0,slotLabel:f,selectedOption:i})}}}const n=globalThis.SCPI;n?.i18n?.init?.();n?.i18n?.apply?.();n?.bus?.start?.();n?.theme?.initThemeDropdown?.();let u=[],f=!1;const t=[{rootId:"rotateLeftDropdown",settingsKey:"rotateLeftFunction",slotLabelKey:"PropertyInspector.DialAction.RotateLeftLabel",slotLabelFallback:"Rotate Left",placeholderKey:"PropertyInspector.DialAction.RotateLeftPlaceholder",placeholderFallback:"Select rotate-left function...",idPrefix:"rotateLeft"},{rootId:"rotateRightDropdown",settingsKey:"rotateRightFunction",slotLabelKey:"PropertyInspector.DialAction.RotateRightLabel",slotLabelFallback:"Rotate Right",placeholderKey:"PropertyInspector.DialAction.RotateRightPlaceholder",placeholderFallback:"Select rotate-right function...",idPrefix:"rotateRight"},{rootId:"pressDropdown",settingsKey:"pressFunction",slotLabelKey:"PropertyInspector.DialAction.PushLabel",slotLabelFallback:"Push",placeholderKey:"PropertyInspector.DialAction.PushPlaceholder",placeholderFallback:"Select push function...",idPrefix:"press"}].map(t=>{const i=n?.ui?.dropdown?.initDropdown?.({rootId:t.rootId,placeholder:{key:t.placeholderKey,fallback:t.placeholderFallback},searchEnabled:!0,minLoadingMs:0,successFlashMs:100,emptyText:{key:"PropertyInspector.Common.Dropdown.NoMatchingFunctionsFound",fallback:"No matching functions found"},maxResults:50,getText:n=>String(n?.text??""),getValue:n=>String(n?.value??""),getGroup:n=>String(n?.group??""),isDisabled:n=>!!n?.disabled,onSelect:n=>s(t.settingsKey,n,{persist:!0})});i?.setLoading?.(!0,{key:"PropertyInspector.Common.Status.LoadingFunctions",fallback:"Loading functions"});const[r,u]=globalThis.SDPIComponents.useSettings(t.settingsKey,n=>{f||o(t.settingsKey,n)});return{...t,dropdown:i,getValue:r,setValue:u,currentValue:""}});n?.ui?.filePicker?.createFilePicker?.({rootId:"audioFilePicker",placeholderText:{key:"PropertyInspector.Common.FilePicker.NoFileSelected",fallback:"No file selected"},buttonText:{key:"PropertyInspector.Common.FilePicker.Button",fallback:"FILE"},selectTitle:{key:"PropertyInspector.Common.Audio.SelectTitle",fallback:"Select audio file"},clearTitle:{key:"PropertyInspector.Common.Audio.ClearTitle",fallback:"Clear audio file"},settingsKey:"clickSoundPath"});const i=document.getElementById("showTouchTextToggle");const[h,c]=globalThis.SDPIComponents.useSettings("showTouchText",n=>{i&&(i.checked=e(n,!0))});i?.addEventListener("change",()=>{c(i.checked)});n?.bus?.on?.(n=>{const i=n?.functionsLoaded;i===!0&&(t.forEach(n=>{n.dropdown?.setLoading?.(!1),document.getElementById(n.rootId)?.classList.remove("pi-dropdown--error")}),a(n.functions||[]));i===!1&&t.forEach(n=>{document.getElementById(n.rootId)?.classList.add("pi-dropdown--error"),n.dropdown?.setLoading?.(!0,{key:"PropertyInspector.Common.Status.NoInstallationDetected",fallback:"No installation detected. Set custom path."}),n.dropdown?.setItems?.([]),r(n,null)})});n?.i18n?.onChange?.(()=>{t.forEach(n=>{const t=u.find(t=>t.value===n.currentValue||t.legacyValue===n.currentValue)||null;r(n,t)})});n?.util?.onDocumentReady?.(()=>{if(i){const n=h();i.checked=e(n,!0)}t.forEach(n=>{const t=n.getValue();n.currentValue=t||"";n.dropdown?.setSelectedValue?.(n.currentValue,{rerender:!1});r(n,null)});n?.bus?.sendOnce?.("pi.connected","propertyInspectorConnected")})})() diff --git a/PluginCore/PropertyInspector/js/src/pi-dial-action.js b/PluginCore/PropertyInspector/js/src/pi-dial-action.js index bafa4c4..d18c392 100644 --- a/PluginCore/PropertyInspector/js/src/pi-dial-action.js +++ b/PluginCore/PropertyInspector/js/src/pi-dial-action.js @@ -89,6 +89,39 @@ settingsKey: 'clickSoundPath' }); + const showTouchTextToggle = document.getElementById('showTouchTextToggle'); + function toBoolean(value, defaultValue = true) { + if (typeof value === 'boolean') { + return value; + } + + if (typeof value === 'string') { + const normalized = value.trim().toLowerCase(); + if (normalized === 'false' || normalized === '0' || normalized === 'off' || normalized === 'no') { + return false; + } + + if (normalized === 'true' || normalized === '1' || normalized === 'on' || normalized === 'yes') { + return true; + } + } + + return defaultValue; + } + + const [getShowTouchText, setShowTouchText] = globalThis.SDPIComponents.useSettings( + 'showTouchText', + (value) => { + if (showTouchTextToggle) { + showTouchTextToggle.checked = toBoolean(value, true); + } + } + ); + + showTouchTextToggle?.addEventListener('change', () => { + setShowTouchText(showTouchTextToggle.checked); + }); + function flattenFunctionsData(groups) { const flatten = SCPI?.functionPicker?.flattenFunctionsData; if (typeof flatten !== 'function') { @@ -218,6 +251,11 @@ }); SCPI?.util?.onDocumentReady?.(() => { + if (showTouchTextToggle) { + const current = getShowTouchText(); + showTouchTextToggle.checked = toBoolean(current, true); + } + slotStates.forEach((slot) => { const savedValue = slot.getValue(); slot.currentValue = savedValue || ''; diff --git a/PluginCore/manifest.json b/PluginCore/manifest.json index e368d29..8810f43 100644 --- a/PluginCore/manifest.json +++ b/PluginCore/manifest.json @@ -25,7 +25,7 @@ "Encoder" ], "Encoder": { - "layout": "$B1", + "layout": "Layouts/adaptiveDial.json", "TriggerDescription": { "Rotate": "Execute left or right function", "Push": "Execute push function" diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 56c2bf8..b70c93e 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -9,12 +9,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - +