Skip to content

Commit

Permalink
Merge pull request #257 from PrefectHQ/bar-sprites
Browse files Browse the repository at this point in the history
Use textures and sprites for node bars
  • Loading branch information
pleek91 authored Oct 23, 2023
2 parents d7222b9 + 6b09ec5 commit bccad61
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 67 deletions.
83 changes: 83 additions & 0 deletions src/factories/bar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { ColorSource, Container } from 'pixi.js'
import { capFactory } from '@/factories/cap'
import { rectangleFactory } from '@/factories/rectangle'

type BarStyle = {
width: number,
height: number,
background: ColorSource,
radius: number,
capLeft?: false,
capRight?: false,
}

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function barFactory() {
const bar = new Container()
const rectangle = await rectangleFactory()
const { left, right, render: renderCaps } = await capFactory()

bar.addChild(rectangle)
bar.addChild(left)
bar.addChild(right)

async function render(style: BarStyle): Promise<Container> {
const { width, x, visible } = getRectangleStyles(style)

await renderCaps(style)

rectangle.visible = visible
rectangle.width = width
rectangle.height = style.height
rectangle.x = x

left.visible = getCapVisibility(style.capLeft, style.radius)
right.visible = getCapVisibility(style.capRight, style.radius)

right.x = style.radius + width

rectangle.tint = style.background
left.tint = style.background
right.tint = style.background

return bar
}

function getCapVisibility(enabled: boolean | undefined, radius: number): boolean {
if (radius === 0) {
return false
}

return enabled ?? true
}

function getRectangleStyles(style: BarStyle): { width: number, x: number, visible: boolean } {
const left = getCapVisibility(style.capLeft, style.radius)
const right = getCapVisibility(style.capRight, style.radius)

let caps = 0

if (left) {
caps += style.radius
}

if (right) {
caps += style.radius
}

const width = Math.max(style.width - caps, 0)
const visible = width > 0
const x = left ? style.radius : 0

return {
width,
visible,
x,
}
}

return {
bar,
render,
}
}
45 changes: 0 additions & 45 deletions src/factories/box.ts

This file was deleted.

58 changes: 58 additions & 0 deletions src/factories/cap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useSubscription } from '@prefecthq/vue-compositions'
import { Graphics, Rectangle, Sprite, Texture } from 'pixi.js'
import { waitForApplication } from '@/objects'

type CapStyle = {
height: number,
radius: number,
}

async function cap({ height, radius }: CapStyle): Promise<Texture> {
const application = await waitForApplication()

const graphic = new Graphics()
graphic.beginFill('#fff')
graphic.drawRoundedRect(0, 0, radius * 2, height, radius)
graphic.endFill()

const cap = application.renderer.generateTexture(graphic, {
// drew a rounded rectangle and then just using half of the graphic to get just the left "cap"
region: new Rectangle(0, 0, radius, height),

// manually bumping up the resolution to keep the border radius from being blurry
resolution: 10,
})

return cap
}

type CapSprites = {
left: Sprite,
right: Sprite,
}

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function capFactory() {
const left = new Sprite()
const right = new Sprite()

async function render(style: CapStyle): Promise<CapSprites> {
const texture = (await useSubscription(cap, [style]).promise()).response
left.texture = texture
right.texture = texture

right.anchor.x = 1
right.scale.x = -1

return {
left,
right,
}
}

return {
left,
right,
render,
}
}
4 changes: 2 additions & 2 deletions src/factories/node.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Container, Ticker } from 'pixi.js'
import { FlowRunContainer, flowRunContainerFactory } from '@/factories/flowRun'
import { TaskRunContainer, taskRunContainerFactory } from '@/factories/taskRun'
import { FlowRunContainer, flowRunContainerFactory } from '@/factories/nodeFlowRun'
import { TaskRunContainer, taskRunContainerFactory } from '@/factories/nodeTaskRun'
import { RunGraphNode } from '@/models/RunGraph'

export type NodeContainerFactory = Awaited<ReturnType<typeof nodeContainerFactory>>
Expand Down
49 changes: 49 additions & 0 deletions src/factories/nodeBar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { differenceInMilliseconds, millisecondsInSecond } from 'date-fns'
import { Container } from 'pixi.js'
import { DEFAULT_LINEAR_COLUMN_SIZE_PIXELS, DEFAULT_TIME_COLUMN_SIZE_PIXELS } from '@/consts'
import { barFactory } from '@/factories/bar'
import { RunGraphNode } from '@/models/RunGraph'
import { waitForConfig } from '@/objects/config'
import { layout } from '@/objects/layout'

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function nodeBarFactory() {
const config = await waitForConfig()
const { bar, render: renderBar } = await barFactory()

async function render(node: RunGraphNode): Promise<Container> {
const { background = '#fff' } = config.styles.node(node)
const { nodeHeight: height, nodeBorderRadius: radius } = config.styles
const width = getTotalWidth(node, radius)
const capLeft = node.state_type !== 'running'

await renderBar({
width,
height,
radius,
background,
capLeft,
})

return bar
}

function getTotalWidth(node: RunGraphNode, borderRadius: number): number {
if (layout.horizontal === 'trace') {
const right = node.start_time
const left = node.end_time ?? new Date()
const seconds = differenceInMilliseconds(left, right) / millisecondsInSecond
const width = seconds * DEFAULT_TIME_COLUMN_SIZE_PIXELS

// this means the min node size is 18px. Is that correct?
return Math.max(width, borderRadius * 2)
}

return DEFAULT_LINEAR_COLUMN_SIZE_PIXELS
}

return {
bar,
render,
}
}
20 changes: 10 additions & 10 deletions src/factories/flowRun.ts → src/factories/nodeFlowRun.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BitmapText, Container, Graphics } from 'pixi.js'
import { BitmapText, Container } from 'pixi.js'
import { DEFAULT_NODE_CONTAINER_NAME } from '@/consts'
import { nodeBoxFactory } from '@/factories/box'
import { nodeLabelFactory } from '@/factories/label'
import { nodeBarFactory } from '@/factories/nodeBar'
import { nodesContainerFactory } from '@/factories/nodes'
import { Pixels } from '@/models/layout'
import { RunGraphNode } from '@/models/RunGraph'
Expand All @@ -13,13 +13,13 @@ export type FlowRunContainer = Awaited<ReturnType<typeof flowRunContainerFactory
export async function flowRunContainerFactory(node: RunGraphNode) {
const container = new Container()
const config = await waitForConfig()
const { box, render: renderBox } = await nodeBoxFactory()
const { bar, render: renderBar } = await nodeBarFactory()
const { label, render: renderLabel } = await nodeLabelFactory()
const { container: nodesContainer, render: renderNodes, stop: stopNodes } = await nodesContainerFactory(node.id)

let open = false

container.addChild(box)
container.addChild(bar)
container.addChild(label)
container.addChild(nodesContainer)

Expand All @@ -35,9 +35,9 @@ export async function flowRunContainerFactory(node: RunGraphNode) {

async function render(node: RunGraphNode): Promise<Container> {
const label = await renderLabel(node)
const box = await renderBox(node)
const bar = await renderBar(node)

label.position = getLabelPosition(label, box)
label.position = getLabelPosition(label, bar)

return container
}
Expand All @@ -54,11 +54,11 @@ export async function flowRunContainerFactory(node: RunGraphNode) {
}
}

function getLabelPosition(label: BitmapText, box: Graphics): Pixels {
function getLabelPosition(label: BitmapText, bar: Container): Pixels {
// todo: this should probably be nodePadding
const margin = config.styles.nodeMargin
const inside = box.width > margin + label.width + margin
const y = box.height / 2 - label.height
const inside = bar.width > margin + label.width + margin
const y = bar.height / 2 - label.height

if (inside) {
return {
Expand All @@ -68,7 +68,7 @@ export async function flowRunContainerFactory(node: RunGraphNode) {
}

return {
x: box.width + margin,
x: bar.width + margin,
y,
}
}
Expand Down
20 changes: 10 additions & 10 deletions src/factories/taskRun.ts → src/factories/nodeTaskRun.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BitmapText, Container, Graphics } from 'pixi.js'
import { BitmapText, Container } from 'pixi.js'
import { DEFAULT_NODE_CONTAINER_NAME } from '@/consts'
import { nodeBoxFactory } from '@/factories/box'
import { nodeLabelFactory } from '@/factories/label'
import { nodeBarFactory } from '@/factories/nodeBar'
import { Pixels } from '@/models/layout'
import { RunGraphNode } from '@/models/RunGraph'
import { waitForConfig } from '@/objects/config'
Expand All @@ -12,30 +12,30 @@ export type TaskRunContainer = Awaited<ReturnType<typeof taskRunContainerFactory
export async function taskRunContainerFactory() {
const container = new Container()
const { label, render: renderLabel } = await nodeLabelFactory()
const { box, render: renderBox } = await nodeBoxFactory()
const { bar, render: renderBar } = await nodeBarFactory()

container.addChild(box)
container.addChild(bar)
container.addChild(label)

container.eventMode = 'none'
container.name = DEFAULT_NODE_CONTAINER_NAME

async function render(node: RunGraphNode): Promise<Container> {
const label = await renderLabel(node)
const box = await renderBox(node)
const bar = await renderBar(node)

label.position = await getLabelPosition(label, box)
label.position = await getLabelPosition(label, bar)

return container
}

async function getLabelPosition(label: BitmapText, box: Graphics): Promise<Pixels> {
async function getLabelPosition(label: BitmapText, bar: Container): Promise<Pixels> {
const config = await waitForConfig()

// todo: this should probably be nodePadding
const margin = config.styles.nodeMargin
const inside = box.width > margin + label.width + margin
const y = box.height / 2 - label.height
const inside = bar.width > margin + label.width + margin
const y = bar.height / 2 - label.height

if (inside) {
return {
Expand All @@ -45,7 +45,7 @@ export async function taskRunContainerFactory() {
}

return {
x: box.width + margin,
x: bar.width + margin,
y,
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/factories/rectangle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useSubscription } from '@prefecthq/vue-compositions'
import { Graphics, RenderTexture, Sprite } from 'pixi.js'
import { waitForApplication } from '@/objects'

async function rectangle(): Promise<RenderTexture> {
const application = await waitForApplication()

const rectangle = new Graphics()
rectangle.beginFill('#fff')
rectangle.drawRect(0, 0, 1, 1)
rectangle.endFill()

const texture = application.renderer.generateTexture(rectangle)

return texture
}

export async function rectangleFactory(): Promise<Sprite> {
const texture = (await useSubscription(rectangle).promise()).response

return new Sprite(texture)
}
Loading

0 comments on commit bccad61

Please sign in to comment.