mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-09 02:49:57 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ced6747737 |
@@ -1,5 +1,5 @@
|
|||||||
import { RGBA, ScrollBoxRenderable, TextAttributes } from "@opentui/core"
|
import { RGBA, ScrollBoxRenderable, TextAttributes } from "@opentui/core"
|
||||||
import { For, Show, createComputed, createEffect, createMemo, createSignal, untrack } from "solid-js"
|
import { For, Show, createComputed, createEffect, createMemo, createSignal, onCleanup, untrack } from "solid-js"
|
||||||
import { useTerminalDimensions } from "@opentui/solid"
|
import { useTerminalDimensions } from "@opentui/solid"
|
||||||
import { useConfig } from "../config"
|
import { useConfig } from "../config"
|
||||||
import { useSessionTabs } from "../context/session-tabs"
|
import { useSessionTabs } from "../context/session-tabs"
|
||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
import { createAnimatable, spring, tween } from "../ui/animation"
|
import { createAnimatable, spring, tween } from "../ui/animation"
|
||||||
import { Locale } from "../util/locale"
|
import { Locale } from "../util/locale"
|
||||||
import { stringWidth } from "../util/string-width"
|
import { stringWidth } from "../util/string-width"
|
||||||
import { TabPulse, unreadGlowIntensity } from "./tab-pulse"
|
import { TabPulse, TabPulseTimeline, unreadGlowIntensity } from "./tab-pulse"
|
||||||
import { tint } from "../theme/color"
|
import { tint } from "../theme/color"
|
||||||
import { SESSION_SIDEBAR_WIDTH } from "../ui/layout"
|
import { SESSION_SIDEBAR_WIDTH } from "../ui/layout"
|
||||||
import { projectName } from "../util/project"
|
import { projectName } from "../util/project"
|
||||||
@@ -97,6 +97,14 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
const itemStatus = (tab: SessionTab) => statuses().get(tab.sessionID)!
|
const itemStatus = (tab: SessionTab) => statuses().get(tab.sessionID)!
|
||||||
|
const pulseTimelines = new Map<string, TabPulseTimeline>()
|
||||||
|
const pulseTimeline = (sessionID: string) => {
|
||||||
|
const existing = pulseTimelines.get(sessionID)
|
||||||
|
if (existing) return existing
|
||||||
|
const created = new TabPulseTimeline()
|
||||||
|
pulseTimelines.set(sessionID, created)
|
||||||
|
return created
|
||||||
|
}
|
||||||
let rail: { screenY: number } | undefined
|
let rail: { screenY: number } | undefined
|
||||||
let scroll: ScrollBoxRenderable | undefined
|
let scroll: ScrollBoxRenderable | undefined
|
||||||
|
|
||||||
@@ -132,6 +140,8 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
|
|||||||
<box flexShrink={0} flexDirection="column" gap={1}>
|
<box flexShrink={0} flexDirection="column" gap={1}>
|
||||||
<For each={items()}>
|
<For each={items()}>
|
||||||
{(tab, index) => {
|
{(tab, index) => {
|
||||||
|
const timeline = pulseTimeline(tab.sessionID)
|
||||||
|
onCleanup(() => pulseTimelines.delete(tab.sessionID))
|
||||||
const selected = () => activeID() === tab.sessionID
|
const selected = () => activeID() === tab.sessionID
|
||||||
const status = createMemo(() => itemStatus(tab))
|
const status = createMemo(() => itemStatus(tab))
|
||||||
const [sweepLevel, setSweepLevel] = createSignal(0)
|
const [sweepLevel, setSweepLevel] = createSignal(0)
|
||||||
@@ -185,6 +195,10 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
|
|||||||
const detailColor = createMemo(() => tint(theme.text.subdued, pulseBackground(), 0.35))
|
const detailColor = createMemo(() => tint(theme.text.subdued, pulseBackground(), 0.35))
|
||||||
const glows = () => status().glows
|
const glows = () => status().glows
|
||||||
const previous = createMemo(() => items()[index() - 1])
|
const previous = createMemo(() => items()[index() - 1])
|
||||||
|
const previousTimeline = () => {
|
||||||
|
const tab = previous()
|
||||||
|
return tab ? pulseTimeline(tab.sessionID) : undefined
|
||||||
|
}
|
||||||
const previousStatus = createMemo(() => {
|
const previousStatus = createMemo(() => {
|
||||||
const tab = previous()
|
const tab = previous()
|
||||||
return tab
|
return tab
|
||||||
@@ -244,24 +258,30 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
|
|||||||
top={-1}
|
top={-1}
|
||||||
edge="above"
|
edge="above"
|
||||||
enabled={animations()}
|
enabled={animations()}
|
||||||
active={runs()}
|
layer={{
|
||||||
outerActive={previousRuns()}
|
timeline,
|
||||||
promptPulse={status().promptPulse}
|
active: runs(),
|
||||||
outerPromptPulse={previousStatus().promptPulse}
|
promptPulse: status().promptPulse,
|
||||||
complete={complete() && !status().attention}
|
complete: complete() && !status().attention,
|
||||||
outerComplete={previousStatus().complete && !previousStatus().attention}
|
glow: glows(),
|
||||||
glow={glows()}
|
breathe: status().attention,
|
||||||
outerGlow={previousGlows()}
|
color: separatorLowerPulseColor(),
|
||||||
breathe={status().attention}
|
glowColor: separatorLowerColor(),
|
||||||
outerBreathe={previousStatus().attention}
|
glowTail: 8,
|
||||||
color={separatorLowerPulseColor()}
|
completionColor: separatorLowerColor(),
|
||||||
outerColor={separatorUpperPulseColor()}
|
}}
|
||||||
glowColor={separatorLowerColor()}
|
edgeLayer={{
|
||||||
outerGlowColor={separatorUpperColor()}
|
timeline: previousTimeline(),
|
||||||
glowTail={8}
|
active: previousRuns(),
|
||||||
outerGlowTail={5}
|
promptPulse: previousStatus().promptPulse,
|
||||||
completionColor={separatorLowerColor()}
|
complete: previousStatus().complete && !previousStatus().attention,
|
||||||
outerCompletionColor={separatorUpperColor()}
|
glow: previousGlows(),
|
||||||
|
breathe: previousStatus().attention,
|
||||||
|
color: separatorUpperPulseColor(),
|
||||||
|
glowColor: separatorUpperColor(),
|
||||||
|
glowTail: 5,
|
||||||
|
completionColor: separatorUpperColor(),
|
||||||
|
}}
|
||||||
backgroundColor={theme.background.default}
|
backgroundColor={theme.background.default}
|
||||||
/>
|
/>
|
||||||
<Show when={index() === items().length - 1}>
|
<Show when={index() === items().length - 1}>
|
||||||
@@ -269,38 +289,41 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
|
|||||||
top={2}
|
top={2}
|
||||||
edge="below"
|
edge="below"
|
||||||
enabled={animations()}
|
enabled={animations()}
|
||||||
active={runs()}
|
layer={{
|
||||||
outerActive={false}
|
timeline,
|
||||||
promptPulse={status().promptPulse}
|
active: runs(),
|
||||||
outerPromptPulse={0}
|
promptPulse: status().promptPulse,
|
||||||
complete={complete() && !status().attention}
|
complete: complete() && !status().attention,
|
||||||
outerComplete={false}
|
glow: glows(),
|
||||||
glow={glows()}
|
breathe: status().attention,
|
||||||
outerGlow={false}
|
color: tint(theme.background.default, theme.text.default, 0.04),
|
||||||
breathe={status().attention}
|
glowColor: tint(theme.background.default, glowHue(), 0.1),
|
||||||
outerBreathe={false}
|
glowTail: 8,
|
||||||
color={tint(theme.background.default, theme.text.default, 0.04)}
|
completionColor: tint(theme.background.default, glowHue(), 0.1),
|
||||||
outerColor={tint(theme.background.default, theme.text.default, 0.006)}
|
}}
|
||||||
glowColor={tint(theme.background.default, glowHue(), 0.1)}
|
edgeLayer={{
|
||||||
outerGlowColor={theme.background.default}
|
color: tint(theme.background.default, theme.text.default, 0.006),
|
||||||
glowTail={8}
|
glowColor: theme.background.default,
|
||||||
outerGlowTail={5}
|
glowTail: 5,
|
||||||
completionColor={tint(theme.background.default, glowHue(), 0.1)}
|
completionColor: theme.background.default,
|
||||||
outerCompletionColor={theme.background.default}
|
}}
|
||||||
backgroundColor={theme.background.default}
|
backgroundColor={theme.background.default}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<box height={1} width="100%" flexDirection="row" position="relative">
|
<box height={1} width="100%" flexDirection="row" position="relative">
|
||||||
<TabPulse
|
<TabPulse
|
||||||
enabled={animations()}
|
enabled={animations()}
|
||||||
active={runs()}
|
layer={{
|
||||||
promptPulse={status().promptPulse}
|
timeline,
|
||||||
complete={complete() && !status().attention}
|
active: runs(),
|
||||||
glow={glows()}
|
promptPulse: status().promptPulse,
|
||||||
breathe={status().attention}
|
complete: complete() && !status().attention,
|
||||||
color={pulseColor()}
|
glow: glows(),
|
||||||
glowColor={glowColor()}
|
breathe: status().attention,
|
||||||
completionColor={glowColor()}
|
color: pulseColor(),
|
||||||
|
glowColor: glowColor(),
|
||||||
|
completionColor: glowColor(),
|
||||||
|
}}
|
||||||
backgroundColor={pulseBackground()}
|
backgroundColor={pulseBackground()}
|
||||||
onLevel={setSweepLevel}
|
onLevel={setSweepLevel}
|
||||||
/>
|
/>
|
||||||
@@ -346,15 +369,18 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
|
|||||||
<box height={1} width="100%" position="relative" flexDirection="row">
|
<box height={1} width="100%" position="relative" flexDirection="row">
|
||||||
<TabPulse
|
<TabPulse
|
||||||
enabled={animations()}
|
enabled={animations()}
|
||||||
active={runs()}
|
layer={{
|
||||||
promptPulse={status().promptPulse}
|
timeline,
|
||||||
complete={complete() && !status().attention}
|
active: runs(),
|
||||||
glow={glows()}
|
promptPulse: status().promptPulse,
|
||||||
breathe={status().attention}
|
complete: complete() && !status().attention,
|
||||||
color={detailPulseColor()}
|
glow: glows(),
|
||||||
glowColor={detailGlowColor()}
|
breathe: status().attention,
|
||||||
glowTail={10}
|
color: detailPulseColor(),
|
||||||
completionColor={detailGlowColor()}
|
glowColor: detailGlowColor(),
|
||||||
|
glowTail: 10,
|
||||||
|
completionColor: detailGlowColor(),
|
||||||
|
}}
|
||||||
backgroundColor={pulseBackground()}
|
backgroundColor={pulseBackground()}
|
||||||
/>
|
/>
|
||||||
<box zIndex={1} width="100%" flexDirection="row" paddingLeft={numberWidth() + 1} paddingRight={2}>
|
<box zIndex={1} width="100%" flexDirection="row" paddingLeft={numberWidth() + 1} paddingRight={2}>
|
||||||
@@ -651,15 +677,17 @@ function HorizontalSessionTabs(props: { controller?: SessionTabsController; anim
|
|||||||
>
|
>
|
||||||
<TabPulse
|
<TabPulse
|
||||||
enabled={animations()}
|
enabled={animations()}
|
||||||
active={status().busy && !status().attention}
|
layer={{
|
||||||
promptPulse={status().promptPulse}
|
active: status().busy && !status().attention,
|
||||||
complete={status().complete && !status().attention}
|
promptPulse: status().promptPulse,
|
||||||
glow={glows()}
|
complete: status().complete && !status().attention,
|
||||||
breathe={status().attention}
|
glow: glows(),
|
||||||
color={pulseColor()}
|
breathe: status().attention,
|
||||||
glowColor={glowColor()}
|
color: pulseColor(),
|
||||||
flashColor={flashColor()}
|
glowColor: glowColor(),
|
||||||
completionColor={accent()}
|
flashColor: flashColor(),
|
||||||
|
completionColor: accent(),
|
||||||
|
}}
|
||||||
backgroundColor={background()}
|
backgroundColor={background()}
|
||||||
onLevel={setSweepLevel}
|
onLevel={setSweepLevel}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,29 +1,28 @@
|
|||||||
import { OptimizedBuffer, Renderable, RGBA, type RenderableOptions, type RenderContext } from "@opentui/core"
|
import { OptimizedBuffer, Renderable, RGBA, type RenderableOptions, type RenderContext } from "@opentui/core"
|
||||||
import { extend } from "@opentui/solid"
|
import { extend } from "@opentui/solid"
|
||||||
|
|
||||||
|
export type TabPulseState = {
|
||||||
|
active?: boolean
|
||||||
|
promptPulse?: number
|
||||||
|
complete?: boolean
|
||||||
|
glow?: boolean
|
||||||
|
breathe?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TabPulseLayer = TabPulseState & {
|
||||||
|
timeline?: TabPulseTimeline
|
||||||
|
color: RGBA
|
||||||
|
glowColor?: RGBA
|
||||||
|
glowTail?: number
|
||||||
|
flashColor?: RGBA
|
||||||
|
completionColor?: RGBA
|
||||||
|
}
|
||||||
|
|
||||||
type TabPulseOptions = RenderableOptions<TabPulseRenderable> & {
|
type TabPulseOptions = RenderableOptions<TabPulseRenderable> & {
|
||||||
edge?: "above" | "below"
|
edge?: "above" | "below"
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
active?: boolean
|
layer?: TabPulseLayer
|
||||||
outerActive?: boolean
|
edgeLayer?: TabPulseLayer
|
||||||
promptPulse?: number
|
|
||||||
outerPromptPulse?: number
|
|
||||||
complete?: boolean
|
|
||||||
outerComplete?: boolean
|
|
||||||
glow?: boolean
|
|
||||||
outerGlow?: boolean
|
|
||||||
breathe?: boolean
|
|
||||||
outerBreathe?: boolean
|
|
||||||
color?: RGBA
|
|
||||||
outerColor?: RGBA
|
|
||||||
glowColor?: RGBA
|
|
||||||
outerGlowColor?: RGBA
|
|
||||||
glowTail?: number
|
|
||||||
outerGlowTail?: number
|
|
||||||
flashColor?: RGBA
|
|
||||||
outerFlashColor?: RGBA
|
|
||||||
completionColor?: RGBA
|
|
||||||
outerCompletionColor?: RGBA
|
|
||||||
backgroundColor?: RGBA
|
backgroundColor?: RGBA
|
||||||
/** Reports the running sweep's intensity at the tab number's cell, quantized; 0 when idle. */
|
/** Reports the running sweep's intensity at the tab number's cell, quantized; 0 when idle. */
|
||||||
onLevel?: (level: number) => void
|
onLevel?: (level: number) => void
|
||||||
@@ -310,71 +309,163 @@ class PulseState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TabPulseTimeline {
|
||||||
|
private state: PulseState
|
||||||
|
private frame = -1
|
||||||
|
|
||||||
|
constructor(options: TabPulseState = {}, enabled = true) {
|
||||||
|
this.state = new PulseState({
|
||||||
|
enabled,
|
||||||
|
active: options.active ?? false,
|
||||||
|
promptPulse: options.promptPulse ?? 0,
|
||||||
|
complete: options.complete ?? false,
|
||||||
|
glow: options.glow ?? false,
|
||||||
|
breathe: options.breathe ?? false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
set(options: TabPulseState, enabled: boolean) {
|
||||||
|
return [
|
||||||
|
this.state.setEnabled(enabled),
|
||||||
|
// Stopping a run arms completion; apply complete afterward so an atomic idle update can consume it.
|
||||||
|
this.state.setActive(options.active ?? false),
|
||||||
|
this.state.setPromptPulse(options.promptPulse ?? 0),
|
||||||
|
this.state.setComplete(options.complete ?? false),
|
||||||
|
this.state.setGlow(options.glow ?? false),
|
||||||
|
this.state.setBreathe(options.breathe ?? false),
|
||||||
|
].some(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnabled(value: boolean) {
|
||||||
|
return this.state.setEnabled(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(deltaTime: number, frame: number) {
|
||||||
|
if (frame === this.frame) return
|
||||||
|
this.frame = frame
|
||||||
|
this.state.advance(deltaTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
get live() {
|
||||||
|
return this.state.live
|
||||||
|
}
|
||||||
|
|
||||||
|
get running() {
|
||||||
|
return this.state.running
|
||||||
|
}
|
||||||
|
|
||||||
|
get completion() {
|
||||||
|
return this.state.completion
|
||||||
|
}
|
||||||
|
|
||||||
|
get flash() {
|
||||||
|
return this.state.flash
|
||||||
|
}
|
||||||
|
|
||||||
|
get glowLevel() {
|
||||||
|
return this.state.glowLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
fronts(width: number) {
|
||||||
|
return this.state.fronts(width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PulseLayer {
|
||||||
|
timeline: TabPulseTimeline
|
||||||
|
private source: TabPulseTimeline | undefined
|
||||||
|
private enabled: boolean
|
||||||
|
color: RGBA
|
||||||
|
glowColor: RGBA
|
||||||
|
glowTail: number
|
||||||
|
flashColor: RGBA
|
||||||
|
completionColor: RGBA
|
||||||
|
|
||||||
|
constructor(options: TabPulseLayer, enabled: boolean) {
|
||||||
|
this.enabled = enabled
|
||||||
|
this.source = options.timeline
|
||||||
|
this.timeline = options.timeline ?? new TabPulseTimeline(options, enabled)
|
||||||
|
if (options.timeline) options.timeline.set(options, enabled)
|
||||||
|
this.color = options.color
|
||||||
|
this.glowColor = options.glowColor ?? options.color
|
||||||
|
this.glowTail = options.glowTail ?? GLOW_TAIL
|
||||||
|
this.flashColor = options.flashColor ?? options.color
|
||||||
|
this.completionColor = options.completionColor ?? options.color
|
||||||
|
}
|
||||||
|
|
||||||
|
set(options: TabPulseLayer) {
|
||||||
|
const sourceChanged = options.timeline !== this.source
|
||||||
|
if (sourceChanged) {
|
||||||
|
this.source = options.timeline
|
||||||
|
this.timeline = options.timeline ?? new TabPulseTimeline(options, this.enabled)
|
||||||
|
}
|
||||||
|
const stateChanged = sourceChanged
|
||||||
|
? (options.timeline?.set(options, this.enabled) ?? false)
|
||||||
|
: this.timeline.set(options, this.enabled)
|
||||||
|
const color = options.color
|
||||||
|
const glowColor = options.glowColor ?? color
|
||||||
|
const glowTail = options.glowTail ?? GLOW_TAIL
|
||||||
|
const flashColor = options.flashColor ?? color
|
||||||
|
const completionColor = options.completionColor ?? color
|
||||||
|
const changed = [
|
||||||
|
sourceChanged,
|
||||||
|
stateChanged,
|
||||||
|
!color.equals(this.color),
|
||||||
|
!glowColor.equals(this.glowColor),
|
||||||
|
glowTail !== this.glowTail,
|
||||||
|
!flashColor.equals(this.flashColor),
|
||||||
|
!completionColor.equals(this.completionColor),
|
||||||
|
].some(Boolean)
|
||||||
|
this.color = color
|
||||||
|
this.glowColor = glowColor
|
||||||
|
this.glowTail = glowTail
|
||||||
|
this.flashColor = flashColor
|
||||||
|
this.completionColor = completionColor
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnabled(value: boolean) {
|
||||||
|
this.enabled = value
|
||||||
|
return this.timeline.setEnabled(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_LAYER: TabPulseLayer = { color: RGBA.defaultForeground() }
|
||||||
|
|
||||||
class TabPulseRenderable extends Renderable {
|
class TabPulseRenderable extends Renderable {
|
||||||
private _enabled: boolean
|
private _enabled: boolean
|
||||||
private inner: PulseState
|
private primary: PulseLayer
|
||||||
private outer: PulseState
|
private adjacent: PulseLayer
|
||||||
private _color: RGBA
|
private primaryAssigned: boolean
|
||||||
private _outerColor: RGBA
|
private adjacentAssigned: boolean
|
||||||
private _glowColor: RGBA
|
|
||||||
private _outerGlowColor: RGBA
|
|
||||||
private _glowTail: number
|
|
||||||
private _outerGlowTail: number
|
|
||||||
private _edge: "above" | "below" | undefined
|
private _edge: "above" | "below" | undefined
|
||||||
private _flashColor: RGBA
|
|
||||||
private _outerFlashColor: RGBA
|
|
||||||
private _completionColor: RGBA
|
|
||||||
private _outerCompletionColor: RGBA
|
|
||||||
private _backgroundColor: RGBA
|
private _backgroundColor: RGBA
|
||||||
private renderColor = RGBA.fromInts(0, 0, 0)
|
private primaryRenderColor = RGBA.fromInts(0, 0, 0)
|
||||||
private outerRenderColor = RGBA.fromInts(0, 0, 0)
|
private adjacentRenderColor = RGBA.fromInts(0, 0, 0)
|
||||||
private _onLevel: ((level: number) => void) | undefined
|
private _onLevel: ((level: number) => void) | undefined
|
||||||
private lastLevel = 0
|
private lastLevel = 0
|
||||||
|
|
||||||
constructor(ctx: RenderContext, options: TabPulseOptions = {}) {
|
constructor(ctx: RenderContext, options: TabPulseOptions = {}) {
|
||||||
const enabled = options.enabled ?? true
|
const enabled = options.enabled ?? true
|
||||||
const active = options.active ?? false
|
const primary = options.layer ?? DEFAULT_LAYER
|
||||||
const glow = options.glow ?? false
|
const adjacent = options.edgeLayer ?? DEFAULT_LAYER
|
||||||
const breathe = options.breathe ?? false
|
|
||||||
const edge = options.edge
|
const edge = options.edge
|
||||||
const outerActive = options.outerActive ?? active
|
|
||||||
const outerGlow = options.outerGlow ?? glow
|
|
||||||
const outerBreathe = options.outerBreathe ?? breathe
|
|
||||||
super(ctx, {
|
super(ctx, {
|
||||||
...options,
|
...options,
|
||||||
height: 1,
|
height: 1,
|
||||||
live:
|
live:
|
||||||
enabled &&
|
enabled &&
|
||||||
(active || (glow && breathe) || (edge !== undefined && (outerActive || (outerGlow && outerBreathe)))),
|
((primary.active ?? false) ||
|
||||||
|
((primary.glow ?? false) && (primary.breathe ?? false)) ||
|
||||||
|
(edge !== undefined &&
|
||||||
|
((adjacent.active ?? false) || ((adjacent.glow ?? false) && (adjacent.breathe ?? false))))),
|
||||||
})
|
})
|
||||||
this._enabled = enabled
|
this._enabled = enabled
|
||||||
this.inner = new PulseState({
|
this.primary = new PulseLayer(primary, enabled)
|
||||||
enabled,
|
this.adjacent = new PulseLayer(adjacent, enabled && edge !== undefined)
|
||||||
active,
|
this.primaryAssigned = options.layer !== undefined
|
||||||
promptPulse: options.promptPulse ?? 0,
|
this.adjacentAssigned = options.edgeLayer !== undefined
|
||||||
complete: options.complete ?? false,
|
|
||||||
glow,
|
|
||||||
breathe,
|
|
||||||
})
|
|
||||||
this.outer = new PulseState({
|
|
||||||
enabled: enabled && edge !== undefined,
|
|
||||||
active: outerActive,
|
|
||||||
promptPulse: options.outerPromptPulse ?? options.promptPulse ?? 0,
|
|
||||||
complete: options.outerComplete ?? options.complete ?? false,
|
|
||||||
glow: outerGlow,
|
|
||||||
breathe: outerBreathe,
|
|
||||||
})
|
|
||||||
this._color = options.color ?? RGBA.defaultForeground()
|
|
||||||
this._outerColor = options.outerColor ?? this._color
|
|
||||||
this._glowColor = options.glowColor ?? this._color
|
|
||||||
this._outerGlowColor = options.outerGlowColor ?? this._glowColor
|
|
||||||
this._glowTail = options.glowTail ?? GLOW_TAIL
|
|
||||||
this._outerGlowTail = options.outerGlowTail ?? this._glowTail
|
|
||||||
this._edge = edge
|
this._edge = edge
|
||||||
this._flashColor = options.flashColor ?? this._color
|
|
||||||
this._outerFlashColor = options.outerFlashColor ?? options.flashColor ?? this._outerColor
|
|
||||||
this._completionColor = options.completionColor ?? this._color
|
|
||||||
this._outerCompletionColor = options.outerCompletionColor ?? options.completionColor ?? this._outerColor
|
|
||||||
this._backgroundColor = options.backgroundColor ?? RGBA.defaultBackground()
|
this._backgroundColor = options.backgroundColor ?? RGBA.defaultBackground()
|
||||||
this._onLevel = options.onLevel
|
this._onLevel = options.onLevel
|
||||||
}
|
}
|
||||||
@@ -388,129 +479,42 @@ class TabPulseRenderable extends Renderable {
|
|||||||
const quantized = Math.round(value * 32) / 32
|
const quantized = Math.round(value * 32) / 32
|
||||||
if (quantized === this.lastLevel) return
|
if (quantized === this.lastLevel) return
|
||||||
this.lastLevel = quantized
|
this.lastLevel = quantized
|
||||||
this._onLevel?.(quantized)
|
this._onLevel(quantized)
|
||||||
}
|
}
|
||||||
|
|
||||||
set enabled(value: boolean) {
|
set enabled(value: boolean) {
|
||||||
if (value === this._enabled) return
|
if (value === this._enabled) return
|
||||||
this._enabled = value
|
this._enabled = value
|
||||||
this.inner.setEnabled(value)
|
this.primary.setEnabled(value)
|
||||||
this.outer.setEnabled(value && this._edge !== undefined)
|
this.adjacent.setEnabled(value && this._edge !== undefined)
|
||||||
this.live = this.inner.live || this.outer.live
|
this.changed()
|
||||||
this.requestRender()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set active(value: boolean) {
|
set layer(value: TabPulseLayer) {
|
||||||
if (this.inner.setActive(value)) this.changed()
|
if (!this.primaryAssigned) {
|
||||||
|
this.primaryAssigned = true
|
||||||
|
this.primary = new PulseLayer(value, this._enabled)
|
||||||
|
this.changed()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.primary.set(value)) this.changed()
|
||||||
}
|
}
|
||||||
|
|
||||||
set outerActive(value: boolean) {
|
set edgeLayer(value: TabPulseLayer) {
|
||||||
if (this.outer.setActive(value)) this.changed()
|
if (!this.adjacentAssigned) {
|
||||||
}
|
this.adjacentAssigned = true
|
||||||
|
this.adjacent = new PulseLayer(value, this._enabled && this._edge !== undefined)
|
||||||
set promptPulse(value: number) {
|
this.changed()
|
||||||
if (this.inner.setPromptPulse(value)) this.changed()
|
return
|
||||||
}
|
}
|
||||||
|
if (this.adjacent.set(value)) this.changed()
|
||||||
set outerPromptPulse(value: number) {
|
|
||||||
if (this.outer.setPromptPulse(value)) this.changed()
|
|
||||||
}
|
|
||||||
|
|
||||||
set complete(value: boolean) {
|
|
||||||
if (this.inner.setComplete(value)) this.changed()
|
|
||||||
}
|
|
||||||
|
|
||||||
set outerComplete(value: boolean) {
|
|
||||||
if (this.outer.setComplete(value)) this.changed()
|
|
||||||
}
|
|
||||||
|
|
||||||
set glow(value: boolean) {
|
|
||||||
if (this.inner.setGlow(value)) this.changed()
|
|
||||||
}
|
|
||||||
|
|
||||||
set outerGlow(value: boolean) {
|
|
||||||
if (this.outer.setGlow(value)) this.changed()
|
|
||||||
}
|
|
||||||
|
|
||||||
set breathe(value: boolean) {
|
|
||||||
if (this.inner.setBreathe(value)) this.changed()
|
|
||||||
}
|
|
||||||
|
|
||||||
set outerBreathe(value: boolean) {
|
|
||||||
if (this.outer.setBreathe(value)) this.changed()
|
|
||||||
}
|
|
||||||
|
|
||||||
private changed() {
|
|
||||||
this.live = this.inner.live || this.outer.live
|
|
||||||
this.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
set color(value: RGBA) {
|
|
||||||
if (value.equals(this._color)) return
|
|
||||||
this._color = value
|
|
||||||
this.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
set glowColor(value: RGBA) {
|
|
||||||
if (value.equals(this._glowColor)) return
|
|
||||||
this._glowColor = value
|
|
||||||
this.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
set outerColor(value: RGBA) {
|
|
||||||
if (value.equals(this._outerColor)) return
|
|
||||||
this._outerColor = value
|
|
||||||
this.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
set outerGlowColor(value: RGBA) {
|
|
||||||
if (value.equals(this._outerGlowColor)) return
|
|
||||||
this._outerGlowColor = value
|
|
||||||
this.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
set glowTail(value: number) {
|
|
||||||
if (value === this._glowTail) return
|
|
||||||
this._glowTail = value
|
|
||||||
this.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
set outerGlowTail(value: number) {
|
|
||||||
if (value === this._outerGlowTail) return
|
|
||||||
this._outerGlowTail = value
|
|
||||||
this.requestRender()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set edge(value: "above" | "below" | undefined) {
|
set edge(value: "above" | "below" | undefined) {
|
||||||
if (value === this._edge) return
|
if (value === this._edge) return
|
||||||
this._edge = value
|
this._edge = value
|
||||||
this.outer.setEnabled(this._enabled && value !== undefined)
|
this.adjacent.setEnabled(this._enabled && value !== undefined)
|
||||||
this.live = this.inner.live || this.outer.live
|
this.changed()
|
||||||
this.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
set flashColor(value: RGBA) {
|
|
||||||
if (value.equals(this._flashColor)) return
|
|
||||||
this._flashColor = value
|
|
||||||
this.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
set outerFlashColor(value: RGBA) {
|
|
||||||
if (value.equals(this._outerFlashColor)) return
|
|
||||||
this._outerFlashColor = value
|
|
||||||
this.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
set completionColor(value: RGBA) {
|
|
||||||
if (value.equals(this._completionColor)) return
|
|
||||||
this._completionColor = value
|
|
||||||
this.requestRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
set outerCompletionColor(value: RGBA) {
|
|
||||||
if (value.equals(this._outerCompletionColor)) return
|
|
||||||
this._outerCompletionColor = value
|
|
||||||
this.requestRender()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set backgroundColor(value: RGBA) {
|
set backgroundColor(value: RGBA) {
|
||||||
@@ -519,38 +523,43 @@ class TabPulseRenderable extends Renderable {
|
|||||||
this.requestRender()
|
this.requestRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private changed() {
|
||||||
|
this.live = this.primary.timeline.live || this.adjacent.timeline.live
|
||||||
|
this.requestRender()
|
||||||
|
}
|
||||||
|
|
||||||
protected override onUpdate(deltaTime: number): void {
|
protected override onUpdate(deltaTime: number): void {
|
||||||
if (!this._enabled) return
|
if (!this._enabled) return
|
||||||
this.inner.advance(deltaTime)
|
this.primary.timeline.advance(deltaTime, this.ctx.frameId)
|
||||||
this.outer.advance(deltaTime)
|
this.adjacent.timeline.advance(deltaTime, this.ctx.frameId)
|
||||||
this.live = this.inner.live || this.outer.live
|
this.live = this.primary.timeline.live || this.adjacent.timeline.live
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override renderSelf(buffer: OptimizedBuffer): void {
|
protected override renderSelf(buffer: OptimizedBuffer): void {
|
||||||
if (!this.visible || this.isDestroyed || this.width <= 0) return
|
if (!this.visible || this.isDestroyed || this.width <= 0) return
|
||||||
const running = this.inner.running
|
const running = this.primary.timeline.running
|
||||||
const completion = this.inner.completion
|
const completion = this.primary.timeline.completion
|
||||||
const flash = this.inner.flash
|
const flash = this.primary.timeline.flash
|
||||||
const glowLevel = this.inner.glowLevel
|
const glowLevel = this.primary.timeline.glowLevel
|
||||||
const outerRunning = this.outer.running
|
const adjacentRunning = this.adjacent.timeline.running
|
||||||
const outerCompletion = this.outer.completion
|
const adjacentCompletion = this.adjacent.timeline.completion
|
||||||
const outerFlash = this.outer.flash
|
const adjacentFlash = this.adjacent.timeline.flash
|
||||||
const outerGlowLevel = this.outer.glowLevel
|
const adjacentGlowLevel = this.adjacent.timeline.glowLevel
|
||||||
if (
|
if (
|
||||||
glowLevel === 0 &&
|
glowLevel === 0 &&
|
||||||
running === 0 &&
|
running === 0 &&
|
||||||
completion === 0 &&
|
completion === 0 &&
|
||||||
flash === 0 &&
|
flash === 0 &&
|
||||||
outerGlowLevel === 0 &&
|
adjacentGlowLevel === 0 &&
|
||||||
outerRunning === 0 &&
|
adjacentRunning === 0 &&
|
||||||
outerCompletion === 0 &&
|
adjacentCompletion === 0 &&
|
||||||
outerFlash === 0
|
adjacentFlash === 0
|
||||||
) {
|
) {
|
||||||
this.emitLevel(0)
|
this.emitLevel(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const [front, secondFront] = this.inner.fronts(this.width)
|
const [front, secondFront] = this.primary.timeline.fronts(this.width)
|
||||||
const [outerFront, outerSecondFront] = this.outer.fronts(this.width)
|
const [adjacentFront, adjacentSecondFront] = this.adjacent.timeline.fronts(this.width)
|
||||||
if (this._onLevel)
|
if (this._onLevel)
|
||||||
this.emitLevel(
|
this.emitLevel(
|
||||||
running === 0
|
running === 0
|
||||||
@@ -558,8 +567,8 @@ class TabPulseRenderable extends Renderable {
|
|||||||
: Math.max(intensityAt(1, front, RUN_HEAD, RUN_TAIL), intensityAt(1, secondFront, RUN_HEAD, RUN_TAIL)) *
|
: Math.max(intensityAt(1, front, RUN_HEAD, RUN_TAIL), intensityAt(1, secondFront, RUN_HEAD, RUN_TAIL)) *
|
||||||
running,
|
running,
|
||||||
)
|
)
|
||||||
const glowTail = Math.min(this._glowTail, Math.max(1, this.width - 2))
|
const glowTail = Math.min(this.primary.glowTail, Math.max(1, this.width - 2))
|
||||||
const outerGlowTail = Math.min(this._outerGlowTail, Math.max(1, this.width - 2))
|
const adjacentGlowTail = Math.min(this.adjacent.glowTail, Math.max(1, this.width - 2))
|
||||||
for (let index = 0; index < this.width; index++) {
|
for (let index = 0; index < this.width; index++) {
|
||||||
// Skip per-cell sweep and glow math when that stage is idle, e.g. a steady breathing glow.
|
// Skip per-cell sweep and glow math when that stage is idle, e.g. a steady breathing glow.
|
||||||
const sweep =
|
const sweep =
|
||||||
@@ -571,49 +580,49 @@ class TabPulseRenderable extends Renderable {
|
|||||||
) *
|
) *
|
||||||
0.14 *
|
0.14 *
|
||||||
running
|
running
|
||||||
const outerSweep =
|
const adjacentSweep =
|
||||||
outerRunning === 0
|
adjacentRunning === 0
|
||||||
? 0
|
? 0
|
||||||
: Math.max(
|
: Math.max(
|
||||||
intensityAt(index, outerFront, RUN_HEAD, RUN_TAIL),
|
intensityAt(index, adjacentFront, RUN_HEAD, RUN_TAIL),
|
||||||
intensityAt(index, outerSecondFront, RUN_HEAD, RUN_TAIL),
|
intensityAt(index, adjacentSecondFront, RUN_HEAD, RUN_TAIL),
|
||||||
) *
|
) *
|
||||||
0.14 *
|
0.14 *
|
||||||
outerRunning
|
adjacentRunning
|
||||||
blendTabPulseColor(
|
blendTabPulseColor(
|
||||||
this.renderColor,
|
this.primaryRenderColor,
|
||||||
this._backgroundColor,
|
this._backgroundColor,
|
||||||
this._glowColor,
|
this.primary.glowColor,
|
||||||
this._color,
|
this.primary.color,
|
||||||
this._flashColor,
|
this.primary.flashColor,
|
||||||
this._completionColor,
|
this.primary.completionColor,
|
||||||
glowLevel === 0 ? 0 : glowIntensityAt(index, glowTail) * GLOW_OPACITY * glowLevel,
|
glowLevel === 0 ? 0 : glowIntensityAt(index, glowTail) * GLOW_OPACITY * glowLevel,
|
||||||
sweep,
|
sweep,
|
||||||
flash,
|
flash,
|
||||||
completion,
|
completion,
|
||||||
)
|
)
|
||||||
if (!this._edge) {
|
if (!this._edge) {
|
||||||
buffer.setCell(this.screenX + index, this.screenY, " ", DEFAULT_FOREGROUND, this.renderColor)
|
buffer.setCell(this.screenX + index, this.screenY, " ", DEFAULT_FOREGROUND, this.primaryRenderColor)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
blendTabPulseColor(
|
blendTabPulseColor(
|
||||||
this.outerRenderColor,
|
this.adjacentRenderColor,
|
||||||
this._backgroundColor,
|
this._backgroundColor,
|
||||||
this._outerGlowColor,
|
this.adjacent.glowColor,
|
||||||
this._outerColor,
|
this.adjacent.color,
|
||||||
this._outerFlashColor,
|
this.adjacent.flashColor,
|
||||||
this._outerCompletionColor,
|
this.adjacent.completionColor,
|
||||||
outerGlowLevel === 0 ? 0 : glowIntensityAt(index, outerGlowTail) * GLOW_OPACITY * outerGlowLevel,
|
adjacentGlowLevel === 0 ? 0 : glowIntensityAt(index, adjacentGlowTail) * GLOW_OPACITY * adjacentGlowLevel,
|
||||||
outerSweep,
|
adjacentSweep,
|
||||||
outerFlash,
|
adjacentFlash,
|
||||||
outerCompletion,
|
adjacentCompletion,
|
||||||
)
|
)
|
||||||
buffer.setCell(
|
buffer.setCell(
|
||||||
this.screenX + index,
|
this.screenX + index,
|
||||||
this.screenY,
|
this.screenY,
|
||||||
this._edge === "above" ? "▄" : "▀",
|
this._edge === "above" ? "▄" : "▀",
|
||||||
this.renderColor,
|
this.primaryRenderColor,
|
||||||
this.outerRenderColor,
|
this.adjacentRenderColor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -632,26 +641,8 @@ export function TabPulse(props: {
|
|||||||
width?: number
|
width?: number
|
||||||
edge?: "above" | "below"
|
edge?: "above" | "below"
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
active: boolean
|
layer: TabPulseLayer
|
||||||
outerActive?: boolean
|
edgeLayer?: TabPulseLayer
|
||||||
promptPulse?: number
|
|
||||||
outerPromptPulse?: number
|
|
||||||
complete?: boolean
|
|
||||||
outerComplete?: boolean
|
|
||||||
glow?: boolean
|
|
||||||
outerGlow?: boolean
|
|
||||||
breathe?: boolean
|
|
||||||
outerBreathe?: boolean
|
|
||||||
color: RGBA
|
|
||||||
outerColor?: RGBA
|
|
||||||
glowColor?: RGBA
|
|
||||||
outerGlowColor?: RGBA
|
|
||||||
glowTail?: number
|
|
||||||
outerGlowTail?: number
|
|
||||||
flashColor?: RGBA
|
|
||||||
outerFlashColor?: RGBA
|
|
||||||
completionColor?: RGBA
|
|
||||||
outerCompletionColor?: RGBA
|
|
||||||
backgroundColor: RGBA
|
backgroundColor: RGBA
|
||||||
onLevel?: (level: number) => void
|
onLevel?: (level: number) => void
|
||||||
}) {
|
}) {
|
||||||
@@ -663,26 +654,8 @@ export function TabPulse(props: {
|
|||||||
zIndex={0}
|
zIndex={0}
|
||||||
width={props.width ?? "100%"}
|
width={props.width ?? "100%"}
|
||||||
enabled={props.enabled ?? true}
|
enabled={props.enabled ?? true}
|
||||||
active={props.active}
|
layer={props.layer}
|
||||||
outerActive={props.outerActive ?? props.active}
|
edgeLayer={props.edge === undefined ? undefined : (props.edgeLayer ?? props.layer)}
|
||||||
promptPulse={props.promptPulse ?? 0}
|
|
||||||
outerPromptPulse={props.outerPromptPulse ?? props.promptPulse ?? 0}
|
|
||||||
complete={props.complete ?? false}
|
|
||||||
outerComplete={props.outerComplete ?? props.complete ?? false}
|
|
||||||
glow={props.glow ?? false}
|
|
||||||
outerGlow={props.outerGlow ?? props.glow ?? false}
|
|
||||||
breathe={props.breathe ?? false}
|
|
||||||
outerBreathe={props.outerBreathe ?? props.breathe ?? false}
|
|
||||||
color={props.color}
|
|
||||||
outerColor={props.outerColor ?? props.color}
|
|
||||||
glowColor={props.glowColor ?? props.color}
|
|
||||||
outerGlowColor={props.outerGlowColor ?? props.glowColor ?? props.color}
|
|
||||||
glowTail={props.glowTail ?? GLOW_TAIL}
|
|
||||||
outerGlowTail={props.outerGlowTail ?? props.glowTail ?? GLOW_TAIL}
|
|
||||||
flashColor={props.flashColor ?? props.color}
|
|
||||||
outerFlashColor={props.outerFlashColor ?? props.flashColor ?? props.outerColor ?? props.color}
|
|
||||||
completionColor={props.completionColor ?? props.color}
|
|
||||||
outerCompletionColor={props.outerCompletionColor ?? props.completionColor ?? props.outerColor ?? props.color}
|
|
||||||
backgroundColor={props.backgroundColor}
|
backgroundColor={props.backgroundColor}
|
||||||
onLevel={props.onLevel}
|
onLevel={props.onLevel}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ test("a prompt pulse restarts the neutral edge flash while the tab remains busy"
|
|||||||
() => (
|
() => (
|
||||||
<box width={8} height={1} backgroundColor={background}>
|
<box width={8} height={1} backgroundColor={background}>
|
||||||
<TabPulse
|
<TabPulse
|
||||||
active={true}
|
layer={{
|
||||||
promptPulse={promptPulse()}
|
active: true,
|
||||||
color={background}
|
promptPulse: promptPulse(),
|
||||||
flashColor={flash}
|
color: background,
|
||||||
|
flashColor: flash,
|
||||||
|
}}
|
||||||
backgroundColor={background}
|
backgroundColor={background}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
|
|||||||
Reference in New Issue
Block a user