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
35 changes: 28 additions & 7 deletions companion/lib/Controls/ControlTypes/Button/LayeredButtonDrawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { ControlLocation } from '@companion-app/shared/Model/Common.js'
import type { ExpressionOrValue } from '@companion-app/shared/Model/Options.js'
import type { SomeButtonGraphicsElement } from '@companion-app/shared/Model/StyleLayersModel.js'
import {
ButtonGraphicsDecorationType,
ButtonGraphicsShowStatusIcons,
type DrawStyleButtonStateProps,
type DrawStyleLayeredButtonModel,
Expand All @@ -30,7 +31,12 @@ export interface DrawElementsVisitor {
*/
export interface LayeredButtonDrawerEntitySource {
getLocalVariableEntities(): ControlEntityInstance[]
getFeedbackStyleOverrides(): ReadonlyMap<string, ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>>
/**
* @param defaultNoTopBar the button's resolved top-bar state, so legacy feedback sizes scale correctly
*/
getFeedbackStyleOverrides(
defaultNoTopBar: boolean | undefined
): ReadonlyMap<string, ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>>
}

/**
Expand Down Expand Up @@ -171,11 +177,6 @@ export class LayeredButtonDrawer {
if (!element.showStatusIcons)
element.showStatusIcons = { value: ButtonGraphicsShowStatusIcons.FollowDefault, isExpression: false }
break
case 'image':
if (!element.fillMode.isExpression && (element.fillMode.value as string) === 'fit_or_shrink') {
element.fillMode.value = 'fit'
}
break
case 'group':
for (const child of element.children) {
this.#normalizeLoadedElement(child)
Expand All @@ -184,6 +185,25 @@ export class LayeredButtonDrawer {
}
}

/**
* Resolve whether this button draws without a top bar, combining the global `buttons_decoration` default
* with the button's own canvas decoration. Used to scale legacy (pre-5.0) feedback font sizes, which are
* converted relative to the available draw height. Resolves from the button's base decoration - a feedback
* that itself overrides the decoration is deliberately not accounted for (that would be a resolution cycle).
*/
resolveDefaultNoTopBar(): boolean {
const canvasElement = this.drawElementsList.find((el) => el.type === 'canvas')
const rawDecoration =
canvasElement?.type === 'canvas' && !canvasElement.decoration.isExpression
? canvasElement.decoration.value
: ButtonGraphicsDecorationType.FollowDefault
const resolvedDecoration =
rawDecoration === ButtonGraphicsDecorationType.FollowDefault
? this.deps.userconfig.getKey('buttons_decoration')
: rawDecoration
return resolvedDecoration !== ButtonGraphicsDecorationType.TopBar
}

/** Compute the draw style of the button. */
async getDrawStyle(): Promise<DrawStyleLayeredButtonModel> {
const injectedVariableValues: VariableValues = {}
Expand All @@ -198,7 +218,8 @@ export class LayeredButtonDrawer {

const locationStr = location ? formatLocation(location) : null

const feedbackOverrides = this.#host.entities?.getFeedbackStyleOverrides() ?? emptyFeedbackOverrides
const feedbackOverrides =
this.#host.entities?.getFeedbackStyleOverrides(this.resolveDefaultNoTopBar()) ?? emptyFeedbackOverrides

const { elements, usedVariables, usedCompositeElements, referencedLocations, cyclicLocations } =
await ConvertSomeButtonGraphicsElementForDrawing(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export class LayeredButtonStyleEditor extends LayeredButtonDrawer {
})
const canvasElement = this.drawElementsList.find((e) => e.type === 'canvas')

const parsedStyle = ParseLegacyStyle(diff)
const parsedStyle = ParseLegacyStyle(diff, this.resolveDefaultNoTopBar())

if (parsedStyle.text.text !== undefined) {
const textElement = lazyTextElement()
Expand Down
9 changes: 4 additions & 5 deletions companion/lib/Controls/Entities/EntityListPoolBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,9 @@ export abstract class ControlEntityListPoolBase {
* Get all the style overrides for the layered drawing elements
* @returns A map of elementId -> elementProperty -> override value
*/
abstract getFeedbackStyleOverrides(): ReadonlyMap<
string,
ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>
>
abstract getFeedbackStyleOverrides(
defaultNoTopBar: boolean | undefined
): ReadonlyMap<string, ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>>

getLocalVariableValues(): VariableValues {
const entities = this.getLocalVariableEntities()
Expand Down Expand Up @@ -315,7 +314,7 @@ export abstract class ControlEntityListPoolBase {
) {
const newOverrides: FeedbackEntityStyleOverride[] = []

const parsedStyle = ParseLegacyStyle(newProps.style)
const parsedStyle = ParseLegacyStyle(newProps.style, undefined)

// Translate the old advanced feedback property lookup into the newly produced value
for (const override of existingStyleOverrides) {
Expand Down
6 changes: 4 additions & 2 deletions companion/lib/Controls/Entities/EntityListPoolButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ export abstract class ButtonEntityListPoolBase extends ControlEntityListPoolBase
return entityLists
}

getFeedbackStyleOverrides(): ReadonlyMap<string, ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>> {
getFeedbackStyleOverrides(
defaultNoTopBar: boolean | undefined
): ReadonlyMap<string, ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>> {
const result = new Map<string, Map<string, ExpressionOrValue<JsonValue | undefined>>>()

const pushOverride = (
Expand Down Expand Up @@ -266,7 +268,7 @@ export abstract class ButtonEntityListPoolBase extends ControlEntityListPoolBase
const style = feedback.feedbackValue
if (!style || typeof style !== 'object') break

const parsedStyle = ParseLegacyStyle(style)
const parsedStyle = ParseLegacyStyle(style, defaultNoTopBar)
for (const override of overrides) {
const newValue = GetLegacyStyleProperty(
parsedStyle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,9 @@ export class EntityListPoolExpressionVariable extends WithEntityEditing(ControlE
this.tryTriggerLocalVariablesChanged(...changedVariableEntities)
}

public getFeedbackStyleOverrides(): ReadonlyMap<
string,
ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>
> {
public getFeedbackStyleOverrides(
_defaultNoTopBar: boolean | undefined
): ReadonlyMap<string, ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>> {
return new Map()
}

Expand Down
7 changes: 3 additions & 4 deletions companion/lib/Controls/Entities/EntityListPoolPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,9 @@ export class EntityListPoolPage extends WithEntityEditing(ControlEntityListPoolB
this.tryTriggerLocalVariablesChanged(...changedVariableEntities)
}

public getFeedbackStyleOverrides(): ReadonlyMap<
string,
ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>
> {
public getFeedbackStyleOverrides(
_defaultNoTopBar: boolean | undefined
): ReadonlyMap<string, ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>> {
return new Map()
}

Expand Down
7 changes: 3 additions & 4 deletions companion/lib/Controls/Entities/EntityListPoolTrigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,9 @@ export class ControlEntityListPoolTrigger extends WithEntityEditing(ControlEntit
this.tryTriggerLocalVariablesChanged(...changedVariableEntities)
}

public getFeedbackStyleOverrides(): ReadonlyMap<
string,
ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>
> {
public getFeedbackStyleOverrides(
_defaultNoTopBar: boolean | undefined
): ReadonlyMap<string, ReadonlyMap<string, ExpressionOrValue<JsonValue | undefined>>> {
return new Map()
}

Expand Down
3 changes: 2 additions & 1 deletion companion/lib/Instance/Connection/PresetsLegacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ function ConvertPresetDefinition(
const parsedStyle = ConvertLegacyStyleToElements(
ConvertPresetStyleToDrawStyle(rawPreset.style),
convertPresetFeedbacksToEntities(rawPreset.feedbacks, entryCtx),
rawPreset.previewStyle
rawPreset.previewStyle,
undefined
)

const presetDefinition: PresetDefinition = {
Expand Down
3 changes: 2 additions & 1 deletion companion/lib/Instance/Connection/Thread/Presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,8 @@ function ConvertPresetDefinition(
const parsedStyle = ConvertLegacyStyleToElements(
ConvertPresetStyleToDrawStyle(rawPreset.style),
convertPresetFeedbacksToEntities(rawPreset.feedbacks, entryCtx),
rawPreset.previewStyle
rawPreset.previewStyle,
undefined
)

const { steps, hasRotaryActions } = ConvertStepsForPreset(entryCtx, rawPreset.steps)
Expand Down
2 changes: 1 addition & 1 deletion companion/lib/Instance/Definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export class InstanceDefinitions extends EventEmitter<InstanceDefinitionsEvents>

if (layeredStyleSelectedElementIds) {
if (definition.feedbackType === FeedbackEntitySubType.Boolean && definition.feedbackStyle) {
const parsedStyle = ParseLegacyStyle(definition.feedbackStyle)
const parsedStyle = ParseLegacyStyle(definition.feedbackStyle, undefined)
feedback.styleOverrides = ConvertBooleanFeedbackStyleToOverrides(
parsedStyle,
layeredStyleSelectedElementIds
Expand Down
2 changes: 1 addition & 1 deletion companion/lib/Preview/ElementStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export class PreviewElementStream {
}
}

const feedbackOverrides = control.entities.getFeedbackStyleOverrides()
const feedbackOverrides = control.entities.getFeedbackStyleOverrides(control.drawing?.resolveDefaultNoTopBar())

if (!elementDef) {
return {
Expand Down
20 changes: 13 additions & 7 deletions companion/lib/Resources/ConvertLegacyStyleToElements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ interface ParsedLegacyStyle {
const TEXT_SIZE_SCALE_NO_TOPBAR = 1 / 0.6 // When no topbar
const TEXT_SIZE_SCALE = 2.1 // When with topbar

export function ParseLegacyStyle(style: Partial<ButtonStyleProperties>, defaultNoTopBar?: boolean): ParsedLegacyStyle {
export function ParseLegacyStyle(
style: Partial<ButtonStyleProperties>,
defaultNoTopBar: boolean | undefined
): ParsedLegacyStyle {
let textSize: number | undefined = undefined
let textSizeAllowShrink: boolean | undefined = undefined
if (style.size !== undefined) {
Expand All @@ -63,8 +66,7 @@ export function ParseLegacyStyle(style: Partial<ButtonStyleProperties>, defaultN
const n = Number(style.size)
if (!isNaN(n)) {
// We can't be 100% accurate on whether to account for the top-bar or not, but during imports we want to try to match how it was just drawing
const showTopBar =
defaultNoTopBar !== undefined && typeof style.show_topbar === 'boolean' ? style.show_topbar : !defaultNoTopBar
const showTopBar = typeof style.show_topbar === 'boolean' ? style.show_topbar : !defaultNoTopBar
const scale = showTopBar ? TEXT_SIZE_SCALE : TEXT_SIZE_SCALE_NO_TOPBAR

// Ensure is a number, and round to 1dp
Expand Down Expand Up @@ -210,7 +212,7 @@ export function ConvertLegacyStyleToElements(
style: ButtonStyleProperties,
feedbacks: SomeEntityModel[],
previewStyle: Partial<ButtonStyleProperties> | null | undefined,
defaultNoTopBar = false
defaultNoTopBar: boolean | undefined
): {
layers: SomeButtonGraphicsElement[]
feedbacks: SomeEntityModel[]
Expand Down Expand Up @@ -256,7 +258,7 @@ export function ConvertLegacyStyleToElements(
base64Image: { value: null, isExpression: false },
halign: { value: 'center', isExpression: false },
valign: { value: 'center', isExpression: false },
fillMode: { value: 'fit', isExpression: false },
fillMode: { value: 'fit_or_shrink', isExpression: false },
}
const textElement: ButtonGraphicsTextElement = {
id: 'text0',
Expand Down Expand Up @@ -302,6 +304,10 @@ export function ConvertLegacyStyleToElements(
// Apply the old style properties to the new elements
const parsedStyle = ParseLegacyStyle(style, defaultNoTopBar)

// Feedback/preview styles rarely carry their own show_topbar, so scale their legacy font sizes relative to
// THIS button's resolved top-bar state (its own show_topbar, else the passed default) rather than the raw default.
const resolvedNoTopBar = typeof style.show_topbar === 'boolean' ? !style.show_topbar : defaultNoTopBar

if (parsedStyle.text.text !== undefined) textElement.text = parsedStyle.text.text
if (parsedStyle.text.size !== undefined) {
textElement.fontsize.value = parsedStyle.text.size
Expand Down Expand Up @@ -338,7 +344,7 @@ export function ConvertLegacyStyleToElements(
if ('style' in fb && fb.style && (Object.keys(fb.style).length > 0 || fb.connectionId !== 'internal')) {
// Must be boolean, translate the props as such

const parsedStyle = ParseLegacyStyle(fb.style, defaultNoTopBar)
const parsedStyle = ParseLegacyStyle(fb.style, resolvedNoTopBar)

overrides = ConvertBooleanFeedbackStyleToOverrides(parsedStyle, selectedElementIds)

Expand Down Expand Up @@ -380,7 +386,7 @@ export function ConvertLegacyStyleToElements(

const previewStyleFeedbacks: SomeEntityModel[] = []
if (previewStyle) {
const parsedStyle = ParseLegacyStyle(previewStyle, defaultNoTopBar)
const parsedStyle = ParseLegacyStyle(previewStyle, resolvedNoTopBar)
const overrides = ConvertBooleanFeedbackStyleToOverrides(parsedStyle, selectedElementIds)

if (overrides.length > 0) {
Expand Down
38 changes: 36 additions & 2 deletions companion/test/Controls/Entities/EntityListPool.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import type { ButtonModelBase } from '@companion-app/shared/Model/ButtonModel.js'
import { EntityModelType, FeedbackEntitySubType } from '@companion-app/shared/Model/EntityModel.js'
import type { ControlEntityListChangeProps } from '../../../lib/Controls/Entities/EntityListPoolBase.js'
import {
ControlEntityListPoolButton,
Expand Down Expand Up @@ -355,7 +356,7 @@ describe('EntityListPool - getFeedbackStyleOverrides (layered button)', () => {
pool.entityAdd('feedbacks', null, feedback)
pool.updateFeedbackValues('conn01', feedbackValues({ [feedback.id]: true }))

const overrides = pool.getFeedbackStyleOverrides()
const overrides = pool.getFeedbackStyleOverrides(undefined)

expect(overrides.get('el1')?.get('color')).toEqual({ isExpression: false, value: 0xff0000 })
})
Expand All @@ -366,7 +367,40 @@ describe('EntityListPool - getFeedbackStyleOverrides (layered button)', () => {
pool.entityAdd('feedbacks', null, feedback)
pool.updateFeedbackValues('conn01', feedbackValues({ [feedback.id]: false }))

expect(pool.getFeedbackStyleOverrides().size).toBe(0)
expect(pool.getFeedbackStyleOverrides(undefined).size).toBe(0)
})

test('legacy advanced-feedback font size scales by the resolved top-bar state', () => {
const { pool } = createPool({
isLayered: true,
getEntityDefinition: (entityType) =>
entityType === EntityModelType.Feedback
? ({ entityType, feedbackType: FeedbackEntitySubType.Advanced } as any)
: ({ entityType } as any),
})
const feedback = feedbackModel({
styleOverrides: [
{
overrideId: 'ov1',
elementId: 'text0',
elementProperty: 'fontsize',
override: { isExpression: false, value: 'size' },
},
],
})
pool.entityAdd('feedbacks', null, feedback)
pool.updateFeedbackValues('conn01', feedbackValues({ [feedback.id]: { size: 14 } }))

// No top bar → full draw height → smaller percentage (the bug: this used to always be 29.4)
expect(pool.getFeedbackStyleOverrides(true).get('text0')?.get('fontsize')).toEqual({
isExpression: false,
value: 23.3,
})
// Top bar → reduced draw height → larger percentage
expect(pool.getFeedbackStyleOverrides(false).get('text0')?.get('fontsize')).toEqual({
isExpression: false,
value: 29.4,
})
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,6 @@ describe('EntityListPoolExpressionVariable', () => {
test('getFeedbackStyleOverrides returns an empty map', () => {
const { pool } = createExpressionVariablePool()

expect(pool.getFeedbackStyleOverrides().size).toBe(0)
expect(pool.getFeedbackStyleOverrides(undefined).size).toBe(0)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,6 @@ describe('ControlEntityListPoolTrigger', () => {
test('getFeedbackStyleOverrides returns an empty map', () => {
const { pool } = createTriggerPool()

expect(pool.getFeedbackStyleOverrides().size).toBe(0)
expect(pool.getFeedbackStyleOverrides(undefined).size).toBe(0)
})
})
Loading
Loading