Loading files
c730ec352This file is pinned to the selected release. Download the original to open it in your design tool.
import { ListScrollView } from "listscrollview.slint";
import { ListViewItemButton } from "listviewitembutton.slint";
import {
Data,
TreeViewItemAction,
TreeViewItemData,
} from "../api.slint";
export component TreeView inherits FocusScope {
in-out property <[TreeViewItemData]> model;
in property <bool> supports-expand: true;
in property <color> icons-color: transparent; // Not colorized by default
in property <length> font-size: 14px;
in property <length> item-height: font-size + 8px; // 22px
in property <length> icon-size: font-size + 4px; // 18px
property <length> pin-size: 14px;
property <int> current-index: -1;
callback item-key-pressed(index: int, item: TreeViewItemData, event: KeyEvent) -> EventResult;
callback control-hovered(index: int, item: TreeViewItemData);
callback item-selected(index: int, item: TreeViewItemData);
callback item-triggered(index: int, item: TreeViewItemData) -> bool;
callback item-right-clicked(index: int, item: TreeViewItemData, x: length, y: length);
callback item-deleted(index: int, item: TreeViewItemData) -> bool;
min-height: min(self.preferred-height, 3 * item-height);
preferred-height: model.length * item-height;
// Accessibility
accessible-role: list;
accessible-item-count: model.length;
function set-current-index(index: int) {
if ((index + 1) * item-height) > (view.height - view.viewport-y) {
view.viewport-y = -(index + 1) * item-height + view.height;
} else if (index * item-height) < -(view.viewport-y) {
view.viewport-y = -(index * item-height);
}
current-index = index;
item-selected(index, model[index]);
}
view := ListScrollView {
x: 0;
y: 0;
width: 100%;
height: 100%;
preferred-height: model.length * item-height;
viewport-height: model.length * item-height;
force-show-scrollbars: root.has-focus;
for item[index] in model: item-ta := TouchArea {
property <bool> is-enabled: item.action == TreeViewItemAction.none;
property <bool> show-pin: item.supports-pinning && (item.pinned || self.has-hover || (current-index == index));
width: parent.width;
height: item-height;
enabled: is-enabled;
// Accessibility
accessible-role: list-item;
accessible-description: item.hint;
accessible-enabled: self.enabled;
accessible-expandable: item.has-children;
accessible-expanded: item.expanded;
accessible-label: item.text;
accessible-item-selectable: true;
accessible-item-selected: (current-index == index);
accessible-item-index: index;
accessible-action-expand => {
item.expanded = !item.expanded;
}
changed has-hover => {
if self.has-hover {
control-hovered(index, item);
} else {
control-hovered(-1, { });
}
}
Rectangle {
background: {
if current-index == index {
Data.theme.control-checked
} else if parent.has-hover {
Data.theme.control-hovered
} else {
transparent
}
};
HorizontalLayout {
spacing: 5px;
padding-left: 5px + (supports-expand ? 0 : item.level * 10px);
padding-right: 5px;
if supports-expand: expand-ta := TouchArea {
mouse-cursor: pointer;
enabled: item.has-children;
width: item.level * 10px + item-height / 2 + 2px;
Image {
y: 4px;
x: parent.width - self.width;
width: item-height - 2 * self.y;
height: parent.height - 2 * self.y;
image-fit: contain;
source: @image-url("../../../font-awesome/svgs/solid/angle-right.svg");
colorize: Data.theme.control-text;
transform-rotation: item.expanded ? 90deg : 0;
visible: item.has-children;
accessible-role: none;
animate transform-rotation { duration: 100ms; }
}
pointer-event(event) => {
if (event.button == PointerEventButton.left) && (event.kind == PointerEventKind.down) {
// Use mouse down event instead of clicked() to make the action
// more responsive (https://github.com/LibrePCB/LibrePCB/issues/1652).
item.expanded = !item.expanded;
}
}
}
Image {
y: (parent.height - self.height) / 2;
width: icon-size;
height: icon-size;
image-fit: fill;
colorize: icons-color;
source: item.icon;
accessible-role: none;
}
item-text-txt := Text {
horizontal-stretch: 1;
color: {
if is-enabled {
Data.theme.control-text
} else {
Data.theme.control-text-disabled
}
};
font-size: font-size;
overflow: TextOverflow.elide;
vertical-alignment: center;
text: item.text;
accessible-role: none;
}
if show-pin: item-pin-btn := ListViewItemButton {
icon: {
if item.pinned {
@image-url("../../../bootstrap-icons/icons/pin-angle-fill.svg")
} else {
@image-url("../../../bootstrap-icons/icons/pin-angle.svg")
}
};
icon-size: pin-size;
tooltip: item.pinned ? @tr("Unpin") : @tr("Pin");
clicked => {
item.pinned = !item.pinned;
}
}
}
}
clicked => {
root.focus();
set-current-index(index);
}
double-clicked => {
if item.has-children {
item.expanded = !item.expanded;
} else if !item-triggered(index, item) {
model[index].action = TreeViewItemAction.open;
}
}
pointer-event(event) => {
if (event.button == PointerEventButton.right) && (event.kind == PointerEventKind.up) {
self.clicked(); // Handle focus & selection
item-right-clicked(index, item, self.absolute-position.x - root.absolute-position.x + self.mouse-x, self.absolute-position.y - root.absolute-position.y + self.mouse-y);
}
}
}
}
key-pressed(event) => {
if event.text == Key.DownArrow {
set-current-index(min(current-index + 1, model.length - 1));
return accept;
} else if event.text == Key.UpArrow {
set-current-index(max(current-index - 1, 0));
return accept;
} else if (current-index >= 0) && (current-index < model.length) {
if event.text == Key.Return {
if !item-triggered(current-index, model[current-index]) {
model[current-index].action = TreeViewItemAction.open;
}
return accept;
} else if (event.text == Key.Space) && (model[current-index].has-children) {
model[current-index].expanded = !model[current-index].expanded;
return accept;
} else if event.text == Key.Delete {
if !item-deleted(current-index, model[current-index]) {
model[current-index].action = TreeViewItemAction.delete;
}
return accept;
} else if event.text == Key.Menu {
item-right-clicked(current-index, model[current-index], 0, item-height * (current-index + 1) + view.viewport-y);
return accept;
}
}
item-key-pressed(current-index, model[current-index], event)
}
}