// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial import { MaterialPalette } from "styling.slint"; component ScrollBar inherits Rectangle { in-out property horizontal; in-out property maximum; in-out property page-size; // this is always negative and bigger than -maximum in-out property value; in-out property enabled <=> i-touch-area.enabled; states [ disabled when !i-touch-area.enabled : { i-background.border-color: MaterialPalette.control-foreground; i-handle.opacity: 0.12; } hover when i-touch-area.has-hover : { i-state-layer.opacity: 0.08; } pressed when i-touch-area.has-hover : { i-state-layer.opacity: 0.12; } ] i-state-layer := Rectangle { width: 100%; height: 100%; background: MaterialPalette.accent-background; border-radius: 4px; opacity: 0; visible: i-handle.width > 0 && i-handle.height > 0; animate opacity { duration: 250ms; easing: ease; } } i-handle := Rectangle { x: !root.horizontal ? 0phx : (root.width - i-handle.width) * (-root.value / root.maximum); y: root.horizontal ? 0phx : (root.height - i-handle.height) * (-root.value / root.maximum); width: !root.horizontal ? parent.width : root.maximum <= 0phx ? 0phx : max(32px, parent.width * max(root.page-size / (root.maximum + root.page-size))); height: root.horizontal ? parent.height : root.maximum <= 0phx ? 0phx : max(32px, parent.height * (root.page-size / (root.maximum + root.page-size))); i-background := Rectangle { width: 100%; height: 100%; border-radius: 4px; border-color: MaterialPalette.border; border-width: 1px; } } i-touch-area := TouchArea { property pressed-value; width: parent.width; height: parent.height; pointer-event(event) => { if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) { self.pressed-value = -root.value; } } moved => { if (self.enabled && self.pressed) { root.value = -max(0px, min(root.maximum, self.pressed-value + ( root.horizontal ? (i-touch-area.mouse-x - i-touch-area.pressed-x) * (root.maximum / (root.width - i-handle.width)) : (i-touch-area.mouse-y - i-touch-area.pressed-y) * (root.maximum / (root.height - i-handle.height)) ))); } } scroll-event(event) => { if (root.horizontal && event.delta-x != 0) { root.value = max(-root.maximum, min(0px, root.value + event.delta-x)); return accept; } else if (!root.horizontal && event.delta-y != 0) { root.value = max(-root.maximum, min(0px, root.value + event.delta-y)); return accept; } reject } } } // Scrollview contains a viewport that is bigger than the view and can be scrolled. export component ScrollView { in property enabled: true; out property visible-width <=> i-flickable.width; out property visible-height <=> i-flickable.height; in-out property has-focus; in-out property viewport-width <=> i-flickable.viewport-width; in-out property viewport-height <=> i-flickable.viewport-height; in-out property viewport-x <=> i-flickable.viewport-x; in-out property viewport-y <=> i-flickable.viewport-y; min-height: 50px; min-width: 50px; horizontal-stretch: 1; vertical-stretch: 1; preferred-height: 100%; preferred-width: 100%; Rectangle { background: MaterialPalette.alternate-background; } i-flickable := Flickable { x:0;y:0; viewport-y <=> i-vertical-bar.value; viewport-x <=> i-horizontal-bar.value; width: parent.width - i-vertical-bar.width - 4px; height: parent.height - i-horizontal-bar.height - 4px; @children } i-vertical-bar := ScrollBar { width: 8px; x: i-flickable.width + i-flickable.x; y: i-flickable.y; height: i-flickable.height; horizontal: false; maximum: i-flickable.viewport-height - i-flickable.height; page-size: i-flickable.height; enabled: root.enabled; } i-horizontal-bar := ScrollBar { height: 8px; y: i-flickable.height + i-flickable.y; x: i-flickable.x; width: i-flickable.width; horizontal: true; maximum: i-flickable.viewport-width - i-flickable.width; page-size: i-flickable.width; enabled: root.enabled; } }