Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 84 additions & 1 deletion companion/lib/Controls/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ControlTrigger } from './ControlTypes/Triggers/Trigger.js'
import { nanoid } from 'nanoid'
import { TriggerEvents } from './TriggerEvents.js'
import debounceFn from 'debounce-fn'
import type { SomeButtonModel } from '@companion-app/shared/Model/ButtonModel.js'
import type { SomeButtonModel, NormalButtonModel } from '@companion-app/shared/Model/ButtonModel.js'
import type { ClientTriggerData, TriggerCollection, TriggerModel } from '@companion-app/shared/Model/TriggerModel.js'
import type { SomeControl } from './IControlFragments.js'
import type { Registry } from '../Registry.js'
Expand All @@ -30,6 +30,8 @@ import { VariablesAndExpressionParser } from '../Variables/VariablesAndExpressio
import LogController from '../Log/Controller.js'
import { DataStoreTableView } from '../Data/StoreBase.js'
import { TriggerCollections } from './TriggerCollections.js'
import { EntityModelType } from '@companion-app/shared/Model/EntityModel.js'
import { ButtonControlBase } from './ControlTypes/Button/Base.js'

export const TriggersListRoom = 'triggers:list'
const ActiveLearnRoom = 'learn:active'
Expand Down Expand Up @@ -302,6 +304,46 @@ export class ControlsController {

return false
})
client.onPromise('controls:shortcut', (fromLocation, toLocation) => {
// Don't try shortcutting to itself
if (
fromLocation.pageNumber === toLocation.pageNumber &&
fromLocation.column === toLocation.column &&
fromLocation.row === toLocation.row
)
return false

// Make sure target page number is valid
if (!this.#registry.page.isPageValid(toLocation.pageNumber)) return false

// Make sure there is something to shortcut to
const fromControlId = this.#registry.page.getControlIdAt(fromLocation)
if (!fromControlId) return false

const fromControl = this.getControl(fromControlId)
if (!fromControl) return false

// Delete the control at the destination
const toControlId = this.#registry.page.getControlIdAt(toLocation)
if (toControlId) {
this.deleteControl(toControlId)
}

const createRotaryActions = fromControl.supportsOptions && fromControl.options.rotaryActions
const newControlJson = this.createShortcutButtonDefinitionModel(fromLocation, createRotaryActions)

const newControlId = CreateBankControlId(nanoid())
const newControl = this.#createClassForControl(newControlId, 'button', newControlJson, true)
if (newControl) {
this.#controls.set(newControlId, newControl)

this.#registry.page.setControlIdAt(toLocation, newControlId)

return true
}

return false
})
client.onPromise('controls:swap', (fromLocation, toLocation) => {
// Don't try moving over itself
if (
Expand Down Expand Up @@ -1317,6 +1359,47 @@ export class ControlsController {
}
}

createShortcutButtonDefinitionModel(
targetLocation: ControlLocation,
createRotaryActions: boolean
): NormalButtonModel {
const locationOptions = {
location_target: 'text',
location_text: formatLocation(targetLocation),
}

const entity = (type: EntityModelType, definitionId: string) => {
const entity = this.#registry.instance.definitions.createEntityItem('internal', type, definitionId)
if (!entity) {
throw new Error(`Failed to create action entity for definition "${definitionId}"`)
}
entity.options = locationOptions
return entity
}

return {
type: 'button',
style: ControlButtonNormal.DefaultStyle,
feedbacks: [entity(EntityModelType.Feedback, 'bank_style')],
steps: {
'0': {
action_sets: {
down: [entity(EntityModelType.Action, 'button_pressrelease')],
up: [],
rotate_left: createRotaryActions ? [entity(EntityModelType.Action, 'button_rotate_left')] : undefined,
rotate_right: createRotaryActions ? [entity(EntityModelType.Action, 'button_rotate_right')] : undefined,
},
options: { runWhileHeld: [] },
},
},
options: {
...ButtonControlBase.DefaultOptions,
rotaryActions: createRotaryActions,
},
localVariables: [],
}
}

createVariablesAndExpressionParser(
controlLocation: ControlLocation | null | undefined,
overrideVariableValues: CompanionVariableValues | null
Expand Down
2 changes: 1 addition & 1 deletion companion/lib/Graphics/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ export class GraphicsRenderer {
}
} else if (drawStyle.style === 'button') {
const textAlign = ParseAlignment(drawStyle.alignment)
const pngAlign = ParseAlignment(drawStyle.pngalignment)
const pngAlign = ParseAlignment(drawStyle.pngalignment ?? 'center:center') // For some reason, pngalignment is not always set (undefined) when using the `bank_style` feedback, while we do expect it to be. This is an existing issue, I haven't looked into it yet.

processedStyle = {
type: 'button',
Expand Down
1 change: 1 addition & 0 deletions shared-lib/lib/SocketIO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export interface ClientToBackendEventsMap extends AllMultipartUploaderMethods {
'controls:set-style-fields': (controlId: string, styleFields: Record<string, any>) => boolean
'controls:move': (from: ControlLocation, to: ControlLocation) => boolean
'controls:copy': (from: ControlLocation, to: ControlLocation) => boolean
'controls:shortcut': (from: ControlLocation, to: ControlLocation) => boolean
'controls:swap': (from: ControlLocation, to: ControlLocation) => boolean
'controls:reset': (location: ControlLocation, newType?: string) => void

Expand Down
23 changes: 22 additions & 1 deletion webui/src/Buttons/ButtonGridActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { CButton, CCol } from '@coreui/react'
import React, { forwardRef, useCallback, useContext, useImperativeHandle, useRef, useState } from 'react'
import { SocketContext } from '~/util.js'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowsLeftRight, faArrowsAlt, faCompass, faCopy, faEraser, faTrash } from '@fortawesome/free-solid-svg-icons'
import {
faArrowsLeftRight,
faArrowsAlt,
faCompass,
faCopy,
faEraser,
faLink,
faTrash,
} from '@fortawesome/free-solid-svg-icons'
import classnames from 'classnames'
import { GenericConfirmModal, GenericConfirmModalRef } from '~/Components/GenericConfirmModal.js'
import { useResizeObserver } from 'usehooks-ts'
Expand Down Expand Up @@ -134,6 +142,17 @@ export const ButtonGridActions = forwardRef<ButtonGridActionsRef, ButtonGridActi
setActiveFunctionButton(location)
}
return true
case 'shortcut':
if (activeFunctionButton) {
const fromInfo = activeFunctionButton
socket.emitPromise('controls:shortcut', [fromInfo, location]).catch((e) => {
console.error(`shortcut failed: ${e}`)
})
stopFunction()
} else {
setActiveFunctionButton(location)
}
return true
case 'move':
if (activeFunctionButton) {
const fromInfo = activeFunctionButton
Expand Down Expand Up @@ -183,6 +202,8 @@ export const ButtonGridActions = forwardRef<ButtonGridActionsRef, ButtonGridActi
&nbsp;
{getButton('Move', faArrowsAlt, 'move')}
&nbsp;
{getButton('Shortcut', faLink, 'shortcut')}
&nbsp;
{getButton('Swap', faArrowsLeftRight, 'swap')}
&nbsp;
{getButton('Delete', faTrash, 'delete')}
Expand Down