From b6fbe3a04f734ba79a3b934dcec2edf503e0be84 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Fri, 5 Jun 2026 18:14:28 +0200 Subject: [PATCH] Add custom scroll handle --- ui/timeline.slint | 87 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/ui/timeline.slint b/ui/timeline.slint index 66fe1c7..6aed104 100644 --- a/ui/timeline.slint +++ b/ui/timeline.slint @@ -97,27 +97,78 @@ component TimelineBlock inherits VerticalLayout { } } -export component Timeline inherits ScrollView { - mouse-drag-pan-enabled: true; - viewport-height: rect.height; +export component ScrollHandle { + out property maximum: 1; + out property minimum: 0; + in-out property value; + callback dragged(float); - changed viewport-y => { - Global.timeline-scrolled(-self.viewport-y); - } - - rect := Rectangle { - y: 0; + width: handle.width * 0.66; + horizontal-stretch: 0; + vertical-stretch: 1; + height: 100%; + + handle := Rectangle { x: 0; - width: root.width; - height: Global.timeline-height; - preferred-width: self.width; - preferred-height: self.height; - for bucket[i] in Global.image-buckets : Rectangle { - if bucket.visibility == Visibility.InView : TimelineBlock { - width: root.width; - index: i; - bucket: bucket; + width: 64px; + height: self.width; + border-width: 3px; + border-radius: self.height / 2; + background: touch.pressed ? Palette.accent-background : Palette.alternate-background; + border-color: Palette.accent-foreground; + y: (root.height - handle.height) * (root.value - root.minimum)/(root.maximum - root.minimum); + + touch := TouchArea { + moved => { + if (self.enabled && self.pressed) { + root.value = max(root.minimum, min(root.maximum, + root.value + (self.mouse-y - self.pressed-y) * (root.maximum - root.minimum) / root.height)); + dragged(root.value) + } } } } } + +export component Timeline { + scroll-view := ScrollView { + mouse-drag-pan-enabled: true; + viewport-height: rect.height; + vertical-scrollbar-policy: always-off; + horizontal-scrollbar-policy: always-off; + + scrolled => { + // sync ScrollHandle with ScrollView + scroll-handle.value = (-scroll-view.viewport-y) / scroll-view.viewport-height; + } + + changed viewport-y => { + Global.timeline-scrolled(-self.viewport-y); + } + + rect := Rectangle { + y: 0; + x: 0; + width: root.width; + height: Global.timeline-height; + preferred-width: self.width; + preferred-height: self.height; + for bucket[i] in Global.image-buckets : Rectangle { + if bucket.visibility == Visibility.InView : TimelineBlock { + width: root.width; + index: i; + bucket: bucket; + } + } + } + } + + scroll-handle := ScrollHandle { + x: parent.x + parent.width - self.width; + height: root.height; + dragged(value) => { + // sync ScrollView with ScrollHandle + scroll-view.viewport-y = -(value * scroll-view.viewport-height); + } + } +}