@gtkx/animated 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -0
- package/dist/animated.d.ts +20 -0
- package/dist/animated.d.ts.map +1 -0
- package/dist/animated.js +44 -0
- package/dist/animated.js.map +1 -0
- package/dist/apply-animated-values.d.ts +4 -0
- package/dist/apply-animated-values.d.ts.map +1 -0
- package/dist/apply-animated-values.js +84 -0
- package/dist/apply-animated-values.js.map +1 -0
- package/dist/bootstrap.d.ts +2 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +11 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/reduced-motion.d.ts +13 -0
- package/dist/reduced-motion.d.ts.map +1 -0
- package/dist/reduced-motion.js +59 -0
- package/dist/reduced-motion.js.map +1 -0
- package/dist/request-frame.d.ts +4 -0
- package/dist/request-frame.d.ts.map +1 -0
- package/dist/request-frame.js +155 -0
- package/dist/request-frame.js.map +1 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/with-animated.d.ts +6 -0
- package/dist/with-animated.d.ts.map +1 -0
- package/dist/with-animated.js +181 -0
- package/dist/with-animated.js.map +1 -0
- package/package.json +78 -0
- package/src/animated.ts +62 -0
- package/src/apply-animated-values.ts +116 -0
- package/src/bootstrap.ts +12 -0
- package/src/index.ts +110 -0
- package/src/reduced-motion.ts +82 -0
- package/src/request-frame.ts +214 -0
- package/src/types.ts +64 -0
- package/src/with-animated.tsx +254 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Lookup } from "@react-spring/types";
|
|
2
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
+
import { applyStyle, applyWrite } from "@gtkx/react/internal";
|
|
4
|
+
import { coerceObjectProperty } from "@gtkx/runtime";
|
|
5
|
+
|
|
6
|
+
const setterCache: WeakMap<object, Map<string, boolean>> = new WeakMap();
|
|
7
|
+
|
|
8
|
+
const hasSetterInChain = (prototype: object | null, name: string): boolean => {
|
|
9
|
+
for (let current = prototype; current !== null; current = Object.getPrototypeOf(current) as object | null) {
|
|
10
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, name);
|
|
11
|
+
|
|
12
|
+
if (descriptor !== undefined) {
|
|
13
|
+
return descriptor.set !== undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return false;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const hasSetter = (instance: object, name: string): boolean => {
|
|
21
|
+
const prototype = Object.getPrototypeOf(instance) as object | null;
|
|
22
|
+
|
|
23
|
+
if (prototype === null) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let known = setterCache.get(prototype);
|
|
28
|
+
|
|
29
|
+
if (known === undefined) {
|
|
30
|
+
known = new Map();
|
|
31
|
+
setterCache.set(prototype, known);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let isWritable = known.get(name);
|
|
35
|
+
|
|
36
|
+
if (isWritable === undefined) {
|
|
37
|
+
isWritable = hasSetterInChain(prototype, name);
|
|
38
|
+
known.set(name, isWritable);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return isWritable;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const isText = (value: unknown): value is string | number => typeof value === "string" || typeof value === "number";
|
|
45
|
+
|
|
46
|
+
const getText = (value: unknown): string | null => {
|
|
47
|
+
if (isText(value)) {
|
|
48
|
+
return String(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!Array.isArray(value)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const texts = value.map((item) => getText(item));
|
|
56
|
+
|
|
57
|
+
return texts.every((item) => item !== null) ? texts.join("") : null;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const didApplyText = (instance: object, value: unknown): boolean => {
|
|
61
|
+
const text = getText(value);
|
|
62
|
+
|
|
63
|
+
if (text === null || !(instance instanceof Gtk.Label)) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
applyWrite(() => {
|
|
68
|
+
instance.setLabel(text);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return true;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const didApplyStyle = (instance: object, value: unknown): boolean => {
|
|
75
|
+
if (!(instance instanceof Gtk.Widget)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
applyStyle(instance, value);
|
|
80
|
+
|
|
81
|
+
return true;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const didApplyValue = (instance: object, name: string, value: unknown): boolean => {
|
|
85
|
+
if (name === "children") {
|
|
86
|
+
return didApplyText(instance, value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (name === "style") {
|
|
90
|
+
return didApplyStyle(instance, value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!hasSetter(instance, name)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
applyWrite(() => {
|
|
98
|
+
Reflect.set(instance, name, coerceObjectProperty(instance, name, value));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return true;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const didApplyAnimatedValues = (instance: object, values: Lookup): boolean => {
|
|
105
|
+
let isApplied = true;
|
|
106
|
+
|
|
107
|
+
for (const name in values) {
|
|
108
|
+
if (!didApplyValue(instance, name, values[name])) {
|
|
109
|
+
isApplied = false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return isApplied;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export { didApplyAnimatedValues };
|
package/src/bootstrap.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Globals } from "@react-spring/core";
|
|
2
|
+
import { colors, createStringInterpolator } from "@react-spring/shared";
|
|
3
|
+
import { trackReducedMotion } from "./reduced-motion.js";
|
|
4
|
+
import { requestFrame } from "./request-frame.js";
|
|
5
|
+
|
|
6
|
+
Globals.assign({
|
|
7
|
+
colors,
|
|
8
|
+
createStringInterpolator,
|
|
9
|
+
requestAnimationFrame: requestFrame,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
trackReducedMotion();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import "./bootstrap.js";
|
|
2
|
+
|
|
3
|
+
export { animated, type Animated } from "./animated.js";
|
|
4
|
+
export { useReducedMotion } from "./reduced-motion.js";
|
|
5
|
+
export type {
|
|
6
|
+
AnimatedComponent,
|
|
7
|
+
AnimatedElements,
|
|
8
|
+
AnimatedItems,
|
|
9
|
+
AnimatedProp,
|
|
10
|
+
AnimatedProps,
|
|
11
|
+
AnimatedStyle,
|
|
12
|
+
} from "./types.js";
|
|
13
|
+
export {
|
|
14
|
+
BailSignal,
|
|
15
|
+
config,
|
|
16
|
+
Controller,
|
|
17
|
+
createInterpolator,
|
|
18
|
+
easings,
|
|
19
|
+
FrameValue,
|
|
20
|
+
Globals,
|
|
21
|
+
inferTo,
|
|
22
|
+
Interpolation,
|
|
23
|
+
Spring,
|
|
24
|
+
SpringContext,
|
|
25
|
+
SpringRef,
|
|
26
|
+
SpringValue,
|
|
27
|
+
to,
|
|
28
|
+
Trail,
|
|
29
|
+
Transition,
|
|
30
|
+
update,
|
|
31
|
+
useChain,
|
|
32
|
+
useSpring,
|
|
33
|
+
useSpringRef,
|
|
34
|
+
useSprings,
|
|
35
|
+
useSpringValue,
|
|
36
|
+
useTrail,
|
|
37
|
+
useTransition,
|
|
38
|
+
} from "@react-spring/core";
|
|
39
|
+
export type {
|
|
40
|
+
AnimationConfig,
|
|
41
|
+
AnimationProps,
|
|
42
|
+
AnimationResult,
|
|
43
|
+
AsyncResult,
|
|
44
|
+
Change,
|
|
45
|
+
ControllerFlushFn,
|
|
46
|
+
ControllerProps,
|
|
47
|
+
ControllerUpdate,
|
|
48
|
+
EventProp,
|
|
49
|
+
ForwardProps,
|
|
50
|
+
GoalProp,
|
|
51
|
+
GoalValue,
|
|
52
|
+
GoalValues,
|
|
53
|
+
InferTo,
|
|
54
|
+
InlineToProps,
|
|
55
|
+
Interpolated,
|
|
56
|
+
Interpolator,
|
|
57
|
+
ItemKeys,
|
|
58
|
+
LoopProp,
|
|
59
|
+
MatchProp,
|
|
60
|
+
OnChange,
|
|
61
|
+
OnPause,
|
|
62
|
+
OnProps,
|
|
63
|
+
OnResolve,
|
|
64
|
+
OnRest,
|
|
65
|
+
OnResume,
|
|
66
|
+
OnStart,
|
|
67
|
+
PickAnimated,
|
|
68
|
+
ReservedEventProps,
|
|
69
|
+
ReservedProps,
|
|
70
|
+
SpringChain,
|
|
71
|
+
SpringComponentProps,
|
|
72
|
+
SpringConfig,
|
|
73
|
+
SpringProps,
|
|
74
|
+
SpringsUpdate,
|
|
75
|
+
SpringTo,
|
|
76
|
+
SpringToFn,
|
|
77
|
+
SpringUpdate,
|
|
78
|
+
SpringUpdateFn,
|
|
79
|
+
SpringValues,
|
|
80
|
+
Springify,
|
|
81
|
+
ToProps,
|
|
82
|
+
ToValues,
|
|
83
|
+
TrailComponentProps,
|
|
84
|
+
TransitionComponentProps,
|
|
85
|
+
TransitionFn,
|
|
86
|
+
TransitionFrom,
|
|
87
|
+
TransitionKey,
|
|
88
|
+
TransitionRenderFn,
|
|
89
|
+
TransitionState,
|
|
90
|
+
TransitionTo,
|
|
91
|
+
TransitionValues,
|
|
92
|
+
UseSpringProps,
|
|
93
|
+
UseSpringsProps,
|
|
94
|
+
UseTrailProps,
|
|
95
|
+
UseTransitionProps,
|
|
96
|
+
VelocityProp,
|
|
97
|
+
} from "@react-spring/core";
|
|
98
|
+
export type { FluidValue } from "@react-spring/shared";
|
|
99
|
+
export type {
|
|
100
|
+
Animatable,
|
|
101
|
+
EasingFunction,
|
|
102
|
+
ExtrapolateType,
|
|
103
|
+
InterpolatorArgs,
|
|
104
|
+
InterpolatorConfig,
|
|
105
|
+
InterpolatorFactory,
|
|
106
|
+
InterpolatorFn,
|
|
107
|
+
Lookup,
|
|
108
|
+
OneOrMore,
|
|
109
|
+
UnknownProps,
|
|
110
|
+
} from "@react-spring/types";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import { Globals } from "@react-spring/core";
|
|
3
|
+
import { useSyncExternalStore } from "react";
|
|
4
|
+
|
|
5
|
+
type Listener = () => void;
|
|
6
|
+
type Tracker = { settings: Gtk.Settings | null; isReduced: boolean | null };
|
|
7
|
+
|
|
8
|
+
const REDUCED_MOTION_MINOR = 22;
|
|
9
|
+
const tracker: Tracker = { settings: null, isReduced: null };
|
|
10
|
+
const listeners: Set<Listener> = new Set();
|
|
11
|
+
|
|
12
|
+
const hasReducedMotionSetting = (): boolean => Gtk.checkVersion(4, REDUCED_MOTION_MINOR, 0) === null;
|
|
13
|
+
|
|
14
|
+
const isReduceMotionPreferred = (settings: Gtk.Settings): boolean =>
|
|
15
|
+
hasReducedMotionSetting() && settings.gtkInterfaceReducedMotion === Gtk.ReducedMotion.REDUCE;
|
|
16
|
+
|
|
17
|
+
const sync = (): void => {
|
|
18
|
+
const { settings } = tracker;
|
|
19
|
+
|
|
20
|
+
if (settings === null) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const isReduced = !settings.gtkEnableAnimations || isReduceMotionPreferred(settings);
|
|
25
|
+
Globals.assign({ skipAnimation: !settings.gtkEnableAnimations });
|
|
26
|
+
|
|
27
|
+
if (isReduced === tracker.isReduced) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
tracker.isReduced = isReduced;
|
|
32
|
+
|
|
33
|
+
for (const listener of listeners) {
|
|
34
|
+
listener();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const watch = (settings: Gtk.Settings): void => {
|
|
39
|
+
tracker.settings = settings;
|
|
40
|
+
settings.on("notify::gtk-enable-animations", sync);
|
|
41
|
+
|
|
42
|
+
if (hasReducedMotionSetting()) {
|
|
43
|
+
settings.on("notify::gtk-interface-reduced-motion", sync);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
sync();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const trackReducedMotion = (): boolean | null => {
|
|
50
|
+
if (tracker.settings === null) {
|
|
51
|
+
const settings = Gtk.Settings.getDefault();
|
|
52
|
+
|
|
53
|
+
if (settings === null) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
watch(settings);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return tracker.isReduced;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const subscribe = (listener: Listener): (() => void) => {
|
|
64
|
+
listeners.add(listener);
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
listeners.delete(listener);
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Reports whether the desktop asks for less motion: `true` while GTK's `gtk-enable-animations`
|
|
73
|
+
* setting is off, which is when every spring already jumps straight to its target, and also while
|
|
74
|
+
* the `gtk-interface-reduced-motion` setting of GTK 4.22 asks to reduce motion, the preference
|
|
75
|
+
* behind the `prefers-reduced-motion` media query, which leaves springs running so that components
|
|
76
|
+
* can trade a slide for a fade themselves. Returns `null` until a display is open.
|
|
77
|
+
*
|
|
78
|
+
* @returns `true` when motion should be reduced, `false` when it should not, `null` when unknown.
|
|
79
|
+
*/
|
|
80
|
+
const useReducedMotion = (): boolean | null => useSyncExternalStore(subscribe, trackReducedMotion);
|
|
81
|
+
|
|
82
|
+
export { trackReducedMotion, useReducedMotion };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import * as GLib from "@gtkx/gi/glib";
|
|
2
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
+
|
|
4
|
+
type FrameCallback = () => void;
|
|
5
|
+
type Timer = ReturnType<typeof setTimeout>;
|
|
6
|
+
type Driver = { widget: Gtk.Widget; tickId: number; stallSource: number };
|
|
7
|
+
|
|
8
|
+
type Scheduler = {
|
|
9
|
+
callbacks: FrameCallback[];
|
|
10
|
+
driver: Driver | null;
|
|
11
|
+
fallbackTimer: Timer | null;
|
|
12
|
+
flushedAt: number;
|
|
13
|
+
isTicking: boolean;
|
|
14
|
+
stalledUntil: WeakMap<Gtk.Widget, number>;
|
|
15
|
+
ticks: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const FALLBACK_FRAME_MS = 16;
|
|
19
|
+
const MIN_FRAME_MS = 1;
|
|
20
|
+
const STALL_MS = 250;
|
|
21
|
+
const STALL_COOLDOWN_MS = 1000;
|
|
22
|
+
|
|
23
|
+
const scheduler: Scheduler = {
|
|
24
|
+
callbacks: [],
|
|
25
|
+
driver: null,
|
|
26
|
+
fallbackTimer: null,
|
|
27
|
+
flushedAt: 0,
|
|
28
|
+
isTicking: false,
|
|
29
|
+
stalledUntil: new WeakMap(),
|
|
30
|
+
ticks: 0,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const isSuspended = (widget: Gtk.Widget): boolean => widget instanceof Gtk.Window && widget.isSuspended();
|
|
34
|
+
|
|
35
|
+
const hasClock = (widget: Gtk.Widget): boolean =>
|
|
36
|
+
widget.getMapped() && widget.getFrameClock() !== null && !isSuspended(widget);
|
|
37
|
+
|
|
38
|
+
const isCoolingDown = (widget: Gtk.Widget): boolean => {
|
|
39
|
+
const until = scheduler.stalledUntil.get(widget);
|
|
40
|
+
|
|
41
|
+
if (until === undefined) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (performance.now() < until) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
scheduler.stalledUntil.delete(widget);
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const findDriverWidget = (): Gtk.Widget | null => {
|
|
55
|
+
const candidates = Gtk.Window.listToplevels().filter((widget) => hasClock(widget));
|
|
56
|
+
|
|
57
|
+
return candidates.find((widget) => !scheduler.stalledUntil.has(widget)) ??
|
|
58
|
+
candidates.find((widget) => !isCoolingDown(widget)) ??
|
|
59
|
+
null;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const flush = (): void => {
|
|
63
|
+
const pending = scheduler.callbacks;
|
|
64
|
+
scheduler.callbacks = [];
|
|
65
|
+
scheduler.isTicking = true;
|
|
66
|
+
scheduler.flushedAt = performance.now();
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
for (const callback of pending) {
|
|
70
|
+
callback();
|
|
71
|
+
}
|
|
72
|
+
} finally {
|
|
73
|
+
scheduler.isTicking = false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const armStallSource = (): number => GLib.timeoutAdd(GLib.PRIORITY_DEFAULT_IDLE, STALL_MS, shouldRepeatStallCheck);
|
|
78
|
+
|
|
79
|
+
const cancelStallSource = (driver: Driver): void => {
|
|
80
|
+
if (driver.stallSource === 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
GLib.Source.remove(driver.stallSource);
|
|
85
|
+
driver.stallSource = 0;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const finishDriver = (driver: Driver): void => {
|
|
89
|
+
cancelStallSource(driver);
|
|
90
|
+
driver.widget.off("unmap", onDriverUnmapped);
|
|
91
|
+
scheduler.driver = null;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const releaseDriver = (driver: Driver): void => {
|
|
95
|
+
finishDriver(driver);
|
|
96
|
+
driver.widget.removeTickCallback(driver.tickId);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const flushThenArm = (): void => {
|
|
100
|
+
try {
|
|
101
|
+
flush();
|
|
102
|
+
} finally {
|
|
103
|
+
arm();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const shouldContinueTicking = (driver: Driver): boolean => {
|
|
108
|
+
if (scheduler.driver !== driver) {
|
|
109
|
+
arm();
|
|
110
|
+
|
|
111
|
+
return GLib.SOURCE_REMOVE;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (scheduler.callbacks.length === 0) {
|
|
115
|
+
finishDriver(driver);
|
|
116
|
+
|
|
117
|
+
return GLib.SOURCE_REMOVE;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
driver.stallSource = armStallSource();
|
|
121
|
+
|
|
122
|
+
return GLib.SOURCE_CONTINUE;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const shouldKeepTicking = (): boolean => {
|
|
126
|
+
const { driver } = scheduler;
|
|
127
|
+
|
|
128
|
+
if (driver === null) {
|
|
129
|
+
return GLib.SOURCE_REMOVE;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (performance.now() - scheduler.flushedAt < MIN_FRAME_MS) {
|
|
133
|
+
return GLib.SOURCE_CONTINUE;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
cancelStallSource(driver);
|
|
137
|
+
scheduler.ticks += 1;
|
|
138
|
+
scheduler.stalledUntil.delete(driver.widget);
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
flush();
|
|
142
|
+
} catch (error) {
|
|
143
|
+
finishDriver(driver);
|
|
144
|
+
arm();
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return shouldContinueTicking(driver);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const onFallbackFrame = (): void => {
|
|
152
|
+
scheduler.fallbackTimer = null;
|
|
153
|
+
flushThenArm();
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const armDriver = (widget: Gtk.Widget): void => {
|
|
157
|
+
const tickId = widget.addTickCallback(shouldKeepTicking);
|
|
158
|
+
scheduler.driver = { widget, tickId, stallSource: armStallSource() };
|
|
159
|
+
widget.on("unmap", onDriverUnmapped);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
function shouldRepeatStallCheck(): boolean {
|
|
163
|
+
const { driver } = scheduler;
|
|
164
|
+
|
|
165
|
+
if (driver !== null) {
|
|
166
|
+
driver.stallSource = 0;
|
|
167
|
+
releaseDriver(driver);
|
|
168
|
+
scheduler.stalledUntil.set(driver.widget, performance.now() + STALL_COOLDOWN_MS);
|
|
169
|
+
flushThenArm();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return GLib.SOURCE_REMOVE;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function onDriverUnmapped(): void {
|
|
176
|
+
const { driver } = scheduler;
|
|
177
|
+
|
|
178
|
+
if (driver === null) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
scheduler.stalledUntil.delete(driver.widget);
|
|
183
|
+
releaseDriver(driver);
|
|
184
|
+
|
|
185
|
+
if (!scheduler.isTicking) {
|
|
186
|
+
arm();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function arm(): void {
|
|
191
|
+
if (scheduler.callbacks.length === 0 || scheduler.driver !== null || scheduler.fallbackTimer !== null) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const widget = findDriverWidget();
|
|
196
|
+
|
|
197
|
+
if (widget === null) {
|
|
198
|
+
scheduler.fallbackTimer = setTimeout(onFallbackFrame, FALLBACK_FRAME_MS);
|
|
199
|
+
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
armDriver(widget);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const requestFrame = (callback: FrameCallback): void => {
|
|
207
|
+
scheduler.callbacks.push(callback);
|
|
208
|
+
|
|
209
|
+
if (!scheduler.isTicking) {
|
|
210
|
+
arm();
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export { requestFrame };
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import type * as elements from "@gtkx/jsx";
|
|
3
|
+
import type { FluidValue } from "@react-spring/shared";
|
|
4
|
+
import type { ComponentPropsWithRef, ElementType, FunctionComponent, Ref } from "react";
|
|
5
|
+
|
|
6
|
+
/** An array-valued prop whose items may each be a spring or an interpolation, such as mixed text children. */
|
|
7
|
+
type AnimatedItems<T> = [Exclude<Extract<T, Iterable<unknown>>, string>] extends [never]
|
|
8
|
+
? never
|
|
9
|
+
: Exclude<Extract<T, Iterable<unknown>>, string> extends Iterable<infer Item>
|
|
10
|
+
? Iterable<AnimatedProp<Item>>
|
|
11
|
+
: never;
|
|
12
|
+
|
|
13
|
+
/** A prop value that an animated component also accepts as a spring or an interpolation. */
|
|
14
|
+
type AnimatedProp<T> = T | FluidValue<Exclude<T, undefined>> | AnimatedItems<Exclude<T, undefined>>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A `style` object whose declarations may each be a spring or an interpolation, nested blocks
|
|
18
|
+
* included, so the object a spring hook returns can be handed to `style` as it is.
|
|
19
|
+
*/
|
|
20
|
+
type AnimatedStyle<T> = {
|
|
21
|
+
[K in keyof T]: T[K] extends string | number | undefined | null
|
|
22
|
+
? AnimatedProp<T[K]>
|
|
23
|
+
: AnimatedProp<T[K]> | AnimatedStyle<NonNullable<T[K]>>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The props of an animated component: every prop of the wrapped component, each also accepting a
|
|
28
|
+
* {@link FluidValue} such as a `SpringValue` or an `Interpolation`, while `ref` and `key` keep
|
|
29
|
+
* their original types.
|
|
30
|
+
*/
|
|
31
|
+
type AnimatedProps<Props extends object> = {
|
|
32
|
+
[P in keyof Props]: P extends "key" | "ref"
|
|
33
|
+
? Props[P]
|
|
34
|
+
: P extends "style"
|
|
35
|
+
? AnimatedProp<Props[P]> | AnimatedStyle<NonNullable<Props[P]>>
|
|
36
|
+
: AnimatedProp<Props[P]>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** A component returned by {@link animated}: the wrapped component with animated props. */
|
|
40
|
+
type AnimatedComponent<T extends Exclude<ElementType, string>> = FunctionComponent<
|
|
41
|
+
AnimatedProps<ComponentPropsWithRef<T>>
|
|
42
|
+
>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The widget components of the generated `@gtkx/jsx` store, keyed by element name, each wrapped as
|
|
46
|
+
* an {@link AnimatedComponent}. Only components whose `ref` exposes a `Gtk.Widget` subclass are
|
|
47
|
+
* included, so `animated.GtkLabel` is available while non-widget elements such as `GtkAdjustment`
|
|
48
|
+
* are wrapped explicitly through the `animated(...)` call instead.
|
|
49
|
+
*/
|
|
50
|
+
type AnimatedElements = {
|
|
51
|
+
readonly [K in keyof typeof elements as (typeof elements)[K] extends Exclude<ElementType, string>
|
|
52
|
+
? ComponentPropsWithRef<(typeof elements)[K]> extends { ref?: Ref<infer Instance> | undefined }
|
|
53
|
+
? [NonNullable<Instance>] extends [never]
|
|
54
|
+
? never
|
|
55
|
+
: NonNullable<Instance> extends Gtk.Widget
|
|
56
|
+
? K
|
|
57
|
+
: never
|
|
58
|
+
: never
|
|
59
|
+
: never]: (typeof elements)[K] extends Exclude<ElementType, string>
|
|
60
|
+
? AnimatedComponent<(typeof elements)[K]>
|
|
61
|
+
: never;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type { AnimatedComponent, AnimatedElements, AnimatedItems, AnimatedProp, AnimatedProps, AnimatedStyle };
|