// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { ListItem, ScrollView } from "std-widgets-impl.slint"; export component ListView inherits ScrollView { @children accessible-role: list; } component StandardListViewBase inherits ListView { in property <[StandardListViewItem]> model; in-out property current-item: -1; callback current-item-changed(current-item: int); callback item-pointer-event(item: int, event: PointerEvent, position: Point); public function set-current-item(index: int) { if index < 0 || index >= model.length || index == root.current-item { return; } bring-into-view(index); current-item = index; focus-item = index; current-item-changed(current-item); } private property item-height: self.viewport-height / self.model.length; private property into-view-item: 0; private property into-view-item-y: root.item-y(root.into-view-item); private property current-item-y: root.item-y(root.focus-item); private property focus-item: 0; private property has-item-been-selected: false; accessible-delegate-focus: root.focus-item; accessible-item-count: root.model.length; pure function first-visible-item() -> int { return min(root.model.length - 1, max(0, round(-root.viewport-y / root.item-height))); } pure function last-visible-item() -> int { return min(root.model.length - 1, max(0, round((-root.viewport-y + root.height - root.item-height) / root.item-height))); } pure function item-y(index: int) -> length { return root.viewport-y + index * root.item-height; } pure function item-at-y(y: length) -> int { return min(root.model.length - 1, max(0, round(y / root.item-height))); } function bring-into-view(index: int) { if (index < 0 || index >= model.length) { return; } into-view-item = index; if (into-view-item-y < 0) { self.viewport-y += 0 - into-view-item-y; } if (into-view-item-y + item-height > self.visible-height) { self.viewport-y -= into-view-item-y + item-height - self.visible-height; } } protected function focus-up() { root.set-focus-item(root.focus-item - 1); } protected function focus-page-up() { if root.focus-item != root.first-visible-item() { root.set-focus-item(root.first-visible-item()) } else { root.set-focus-item(root.item-at-y(root.item-y(root.first-visible-item()) - root.height)); } } protected function focus-first() { root.set-focus-item(0); } protected function focus-down() { root.set-focus-item(root.focus-item + 1); } protected function focus-page-down() { if root.focus-item != root.last-visible-item() { root.set-focus-item(root.last-visible-item()) } else { root.set-focus-item(root.item-at-y(root.item-y(root.last-visible-item()) + root.height)); } } protected function focus-last() { root.set-focus-item(root.model.length - 1); } protected function select-focus-item() { root.set-current-item(root.focus-item); } protected function focus-current-item() { if root.current-item == -1 && !root.has-item-been-selected && root.model.length > 0 { root.set-current-item(0); } root.has-item-been-selected = true; if (root.current-item-y + root.item-height < 0 || root.current-item-y > root.height) { root.focus-item = root.first-visible-item(); } } protected function toggle-focus-item-selection() { if (root.current-item == root.focus-item) { root.current-item = -1; } else { root.select-focus-item(); } } protected function set-focus-item(index: int) { root.focus-item = min(root.model.length - 1, max(0, index)); root.bring-into-view(root.focus-item); } for item[index] in root.model : ListItem { height: self.min-height; item: item; index: index; is-selected: index == root.current-item; has-focus: root.has-focus && index == root.focus-item; has-hover: i-touch-area.has-hover; pressed: i-touch-area.pressed; pressed-x: i-touch-area.pressed-x; pressed-y: i-touch-area.pressed-y; accessible-action-default => { i-touch-area.clicked(); } i-touch-area := TouchArea { clicked => { root.set-current-item(index); } pointer-event(pe) => { root.item-pointer-event(index, pe, { x: self.absolute-position.x + self.mouse-x - root.absolute-position.x, y: self.absolute-position.y + self.mouse-y - root.absolute-position.y, }); } } } } export component StandardListView inherits StandardListViewBase { forward-focus: i-focus-scope; i-focus-scope := FocusScope { x: 0; width: 0; // Do not react on clicks focus-changed-event => { root.focus-current-item(); root.has-focus = self.has-focus; } key-pressed(event) => { if (event.text == Key.UpArrow) { root.focus-up(); if (!event.modifiers.control) { root.select-focus-item(); } return accept; } else if (event.text == Key.PageUp) { root.focus-page-up(); if (!event.modifiers.control) { root.select-focus-item(); } return accept; } else if (event.text == Key.Home) { root.focus-first(); if (!event.modifiers.control) { root.select-focus-item(); } return accept; } else if (event.text == Key.DownArrow) { root.focus-down(); if (!event.modifiers.control) { root.select-focus-item(); } return accept; } else if (event.text == Key.PageDown) { root.focus-page-down(); if (!event.modifiers.control) { root.select-focus-item(); } return accept; } else if (event.text == Key.End) { root.focus-last(); if (!event.modifiers.control) { root.select-focus-item(); } return accept; } else if (event.text == Key.Space) { if (event.modifiers.control) { root.toggle-focus-item-selection(); } else { root.select-focus-item(); } return accept; } reject } } }