// Player seek bar — elapsed time, a three-line track (total / cached / // progress) with a hover thumb, and the remaining time. On hover the // cursor shows a time bubble with a downward caret marking the position // it would seek to. import { Theme } from "../foundation/semantic-colors.slint"; import { Typography } from "../foundation/typography.slint"; export component SeekBar inherits HorizontalLayout { in property progress: 0.0; // 0..1 played in property cache: 0.0; // 0..1 buffered in property elapsed: "0:00"; in property remaining: "0:00"; in property duration-secs: 0; callback seek(float); // 0..1 spacing: 12px; function clamp01(v: float) -> float { return Math.min(Math.max(v, 0.0), 1.0); } function fmt(secs: int) -> string { return "\{Math.floor(secs / 60)}:\{Math.mod(secs, 60) < 10 ? "0" : ""}\{Math.mod(secs, 60)}"; } VerticalLayout { alignment: center; Text { text: root.elapsed; width: 44px; horizontal-alignment: right; color: Theme.text-muted; font-size: 11px; } } VerticalLayout { alignment: center; horizontal-stretch: 1; track := Rectangle { height: 3px; border-radius: 2px; background: Theme.surface-elevated; // Buffered / cache line. Rectangle { x: 0; y: 0; width: parent.width * root.clamp01(root.cache); height: parent.height; border-radius: 2px; background: Theme.text-muted; opacity: 0.35; } // Playback progress line. Rectangle { x: 0; y: 0; width: parent.width * root.clamp01(root.progress); height: parent.height; border-radius: 2px; background: Theme.accent; } // Hover thumb. Rectangle { width: 12px; height: 12px; border-radius: 6px; background: Theme.text-primary; y: (parent.height - self.height) / 2; x: parent.width * root.clamp01(root.progress) - self.width / 2; opacity: ta.has-hover ? 1.0 : 0.0; animate opacity { duration: 100ms; } } // Tall, easy-to-grab hit area over the thin line. ta := TouchArea { width: 100%; height: 18px; y: (parent.height - self.height) / 2; mouse-cursor: pointer; clicked => { root.seek(root.clamp01(self.mouse-x / self.width)); } } // Hover time tooltip with a downward caret. tip := Rectangle { property at-secs: Math.round(root.clamp01(ta.mouse-x / track.width) * root.duration-secs); x: Math.min(Math.max(ta.mouse-x - self.width / 2, 0px), track.width - self.width); y: -self.height - 11px; width: bubble.preferred-width; height: 24px; opacity: ta.has-hover ? 1.0 : 0.0; animate opacity { duration: 80ms; } bubble := Rectangle { background: Theme.surface-elevated; border-radius: 4px; drop-shadow-blur: 10px; drop-shadow-color: #00000099; HorizontalLayout { padding-left: 9px; padding-right: 9px; Text { text: root.fmt(tip.at-secs); color: Theme.text-primary; font-size: 11px; font-weight: Typography.medium; vertical-alignment: center; } } } // Caret — a small downward triangle under the bubble. Path { width: 10px; height: 6px; fill: Theme.surface-elevated; commands: "M 0 0 L 5 6 L 10 0 Z"; x: (parent.width - self.width) / 2; y: parent.height - 1px; } } } } VerticalLayout { alignment: center; Text { text: root.remaining; width: 44px; horizontal-alignment: left; color: Theme.text-muted; font-size: 11px; } } }