added focus state to StandardLIstView (#4086)

* added focus state to StandardLIstView

* (wip) focus handling for qt style

* Update CHANGELOG.md

Co-authored-by: Thorbjørn Lindeijer <bjorn@lindeijer.nl>

* Share StandardListView between all styles

* Update internal/compiler/widgets/material-base/combobox.slint

Co-authored-by: Thorbjørn Lindeijer <bjorn@lindeijer.nl>

* code review feedback

---------

Co-authored-by: Thorbjørn Lindeijer <bjorn@lindeijer.nl>
This commit is contained in:
Florian Blasius 2023-12-08 10:58:58 +01:00 committed by GitHub
parent f25a419009
commit 6d6b18300a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 337 additions and 403 deletions

View file

@ -8,6 +8,7 @@ All notable changes to this project are documented in this file.
### Slint Language
- Fixed wrong text input in cupertino SpinBox
- Added focus state to `StandardListView`
## [1.3.2] - 2023-12-01

View file

@ -14,6 +14,11 @@ pub struct NativeStandardListViewItem {
pub is_selected: Property<bool>,
pub cached_rendering_data: CachedRenderingData,
pub has_hover: Property<bool>,
pub has_focus: Property<bool>,
pub pressed: Property<bool>,
pub pressed_x: Property<LogicalLength>,
pub pressed_y: Property<LogicalLength>,
/// Specify that this item is in fact used in a ComboBox
pub combobox: Property<bool>,
widget_ptr: std::cell::Cell<SlintTypeErasedWidgetPtr>,
@ -113,6 +118,7 @@ impl Item for NativeStandardListViewItem {
let is_selected: bool = this.is_selected();
let combobox: bool = this.combobox();
let has_hover: bool = this.has_hover();
let has_focus: bool = this.has_focus();
let item = this.item();
let text: qttypes::QString = item.text.as_str().into();
cpp!(unsafe [
@ -123,6 +129,7 @@ impl Item for NativeStandardListViewItem {
index as "int",
is_selected as "bool",
has_hover as "bool",
has_focus as "bool",
text as "QString",
initial_state as "int",
combobox as "bool"
@ -140,6 +147,11 @@ impl Item for NativeStandardListViewItem {
option.state |= QStyle::State_MouseOver;
option.state |= QStyle::State_Selected;
}
if (has_focus) {
option.state |= QStyle::State_HasFocus;
option.state |= QStyle::State_Selected;
}
option.text = text;
option.text.replace(QChar('&'), QLatin1String("&&"));
option.checked = is_selected;
@ -161,6 +173,9 @@ impl Item for NativeStandardListViewItem {
if (has_hover) {
option.state |= QStyle::State_MouseOver;
}
if (has_focus) {
option.state |= QStyle::State_HasFocus;
}
option.decorationPosition = QStyleOptionViewItem::Left;
option.decorationAlignment = Qt::AlignCenter;
option.displayAlignment = Qt::AlignLeft|Qt::AlignVCenter;

View file

@ -455,7 +455,11 @@ export component NativeStandardListViewItem {
in property <StandardListViewItem> item;
in-out property <bool> is_selected;
in property <bool> has_hover;
in property <bool> has_focus;
in property <bool> pressed;
in property <bool> combobox;
in property <length> pressed-x;
in property <length> pressed-y;
//-is_internal
}

View file

@ -0,0 +1,137 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { ListItem, ScrollView } from "std-widgets-impl.slint";
export component ListView inherits ScrollView {
@children
}
component StandardListViewBase inherits ListView {
in property <[StandardListViewItem]> model;
in-out property <int> current-item: -1;
callback current-item-changed(/* current-item */ int);
callback item-pointer-event( /* item-index */ int, /* event */ PointerEvent, /* absolute mouse position */ Point);
public function set-current-item(index: int) {
if (index < 0 || index >= model.length) {
return;
}
bring-into-view(index);
current-item = index;
focus-item = index;
current-item-changed(current-item);
}
private property <length> item-height: self.viewport-height / self.model.length;
private property <int> into-view-item: 0;
private property <length> into-view-item-y: root.item-y(root.into-view-item);
private property <length> current-item-y: root.item-y(root.focus-item);
private property <int> focus-item: 0;
pure function first-visible-item() -> int {
return min(root.model.length - 1, max(0, round(-root.viewport-y / root.item-height)));
}
pure function item-y(index: int) -> length {
return root.viewport-y + index * 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-down() {
root.set-focus-item(root.focus-item + 1);
}
protected function select-focus-item() {
root.set-current-item(root.focus-item);
}
protected function focus-current-item() {
root.focus-item = max(0, root.current-item);
if (root.current-item-y + root.item-height < 0
|| root.current-item-y > root.height) {
root.focus-item = root.first-visible-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;
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();
return accept;
} else if (event.text == Key.DownArrow) {
root.focus-down();
return accept;
}else if (event.text == Key.Return) {
root.select-focus-item();
return accept;
}
reject
}
}
}

View file

@ -154,11 +154,17 @@ export component ComboBox {
for value[index] in root.model : ListItem {
padding-horizontal: 0;
text: value;
selected: index == root.current-index;
item: { text: value };
is-selected: index == root.current-index;
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;
clicked => {
i-base.select(index);
i-touch-area := TouchArea {
clicked => {
i-base.select(index);
}
}
}
}

View file

@ -44,14 +44,15 @@ export component MenuBorder inherits Rectangle {
}
export component ListItem {
in property <bool> selected;
in property <string> text <=> i-text.text;
in property <bool> is-selected;
in property <StandardListViewItem> item;
in property <length> padding-horizontal: 12px;
out property <length> mouse-x <=> i-touch-area.mouse-x;
out property <length> mouse-y <=> i-touch-area.mouse-y;
callback clicked <=> i-touch-area.clicked;
callback pointer-event <=> i-touch-area.pointer-event;
in property <bool> has-focus;
in property <bool> has-hover;
in property <bool> pressed;
in property <int> index;
in property <length> pressed-x;
in property <length> pressed-y;
min-width: i-layout.min-width;
min-height: max(22px, i-layout.min-height);
@ -59,7 +60,10 @@ export component ListItem {
horizontal-stretch: 1;
states [
hover when i-touch-area.has-hover : {
has-focus when root.has-focus : {
i-background.background: CupertinoPalette.accent-tertiary;
}
hover when root.has-hover : {
i-background.background: CupertinoPalette.accent;
i-text.color: CupertinoPalette.on-surface;
i-icon.colorize: CupertinoPalette.on-surface;
@ -83,11 +87,12 @@ export component ListItem {
image-fit: contain;
source: Icons.check-mark;
colorize: CupertinoPalette.foreground;
visible: root.selected;
visible: root.is-selected;
width: 10px;
}
i-text := Text {
text: root.item.text;
color: CupertinoPalette.foreground;
font-size: CupertinoFontSettings.body.font-size;
font-weight: CupertinoFontSettings.body.font-weight;
@ -99,5 +104,5 @@ export component ListItem {
}
}
i-touch-area := TouchArea {}
@children
}

View file

@ -1,72 +0,0 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { ScrollView } from "scrollview.slint";
import { ListItem } from "components.slint";
export component ListView inherits ScrollView {
@children
}
component StandardListViewBase inherits ListView {
in property <[StandardListViewItem]> model;
in-out property <int> current-item: -1;
callback current-item-changed(int /* current-item */);
callback item-pointer-event(int /* item-index */, PointerEvent /* event */, Point /* absolute mouse position */);
public function set-current-item(index: int) {
if(index < 0 || index >= model.length) {
return;
}
current-item = index;
current-item-changed(current-item);
if(current-item-y < 0) {
self.viewport-y += 0 - current-item-y;
}
if(current-item-y + item-height > self.visible-height) {
self.viewport-y -= current-item-y + item-height - self.visible-height;
}
}
private property <length> item-height: self.viewport-height / self.model.length;
private property <length> current-item-y: self.viewport-y + current-item * item-height;
for item[index] in root.model : ListItem {
height: self.min-height;
text: item.text;
selected: index == root.current-item;
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 {
FocusScope {
x: 0;
width: 0; // Do not react on clicks
key-pressed(event) => {
if (event.text == Key.UpArrow) {
root.set-current-item(root.current-item - 1);
return accept;
} else if (event.text == Key.DownArrow) {
root.set-current-item(root.current-item + 1);
return accept;
}
reject
}
}
}

View file

@ -19,7 +19,7 @@ export { GroupBox }
import { LineEdit } from "lineedit.slint";
export { LineEdit }
import { ListView, StandardListView } from "listview.slint";
import { ListView, StandardListView } from "../common/listview.slint";
export { ListView, StandardListView }
import { ProgressIndicator } from "progressindicator.slint";

View file

@ -9,6 +9,9 @@ export { Button }
import { ScrollView } from "scrollview.slint";
export { ScrollView }
import { ListItem } from "components.slint";
export { ListItem }
import { CupertinoPalette, CupertinoFontSettings } from "styling.slint";
export global StyleMetrics {

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { CupertinoPalette, CupertinoFontSettings, Icons } from "styling.slint";
import { ListView } from "listview.slint";
import { ListView } from "../common/listview.slint";
component TableViewColumn inherits Rectangle {
in property <SortOrder> sort-order: SortOrder.unsorted;

View file

@ -1,5 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { StyleMetrics, ScrollView, Button } from "../cupertino-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button }
import { StyleMetrics, ScrollView, Button, ListItem } from "../cupertino-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, ListItem }

View file

@ -1,5 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { StyleMetrics, ScrollView, Button } from "../cupertino-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button }
import { StyleMetrics, ScrollView, Button, ListItem } from "../cupertino-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, ListItem }

View file

@ -1,5 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { StyleMetrics, ScrollView, Button } from "../fluent-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button }
import { StyleMetrics, ScrollView, Button, ListItem } from "../cupertino-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, ListItem }

View file

@ -97,11 +97,15 @@ export component ComboBox {
padding: 4px;
for value[index] in root.model : ListItem {
text: value;
selected: index == root.current-index;
item: { text: value };
is-selected: index == root.current-index;
has-hover: i-touch-area.has-hover;
pressed: i-touch-area.pressed;
clicked => {
i-base.select(index);
i-touch-area := TouchArea {
clicked => {
i-base.select(index);
}
}
}
}

View file

@ -33,35 +33,42 @@ export component MenuBorder inherits Rectangle {
}
export component ListItem {
in property <bool> selected;
in property <string> text <=> i-text.text;
out property <length> mouse-x <=> i-touch-area.mouse-x;
out property <length> mouse-y <=> i-touch-area.mouse-y;
callback clicked <=> i-touch-area.clicked;
callback pointer-event <=> i-touch-area.pointer-event;
in property <bool> is-selected;
in property <StandardListViewItem> item;
in property <bool> has-focus;
in property <bool> has-hover;
in property <bool> pressed;
in property <int> index;
in property <length> pressed-x;
in property <length> pressed-y;
min-width: i-layout.min-width;
min-height: max(34px, i-layout.min-height);
min-height: max(40px, i-layout.min-height);
vertical-stretch: 0;
horizontal-stretch: 1;
states [
pressed when i-touch-area.pressed : {
i-background.background: selected ? FluentPalette.subtle-secondary : FluentPalette.subtle-tertiary;
pressed when root.pressed : {
i-background.background: is-selected ? FluentPalette.subtle-secondary : FluentPalette.subtle-tertiary;
}
hover when i-touch-area.has-hover : {
hover when root.has-hover : {
i-text.color: FluentPalette.text-secondary;
i-background.background: selected ? FluentPalette.subtle-tertiary : FluentPalette.subtle-secondary;
i-selector.height: root.selected ? 16px : 0;
i-background.background: is-selected ? FluentPalette.subtle-tertiary : FluentPalette.subtle-secondary;
i-selector.height: root.is-selected ? 16px : 0;
}
selected when root.selected : {
is-selected when root.is-selected : {
i-background.background: FluentPalette.subtle-secondary;
i-selector.height: 16px;
}
]
if (root.has-focus) : FocusBorder {
border-radius: 4px;
}
i-background := Rectangle {
width: root.width - 6px;
height: root.height - 4px;
background: transparent;
border-radius: 4px;
@ -73,6 +80,7 @@ export component ListItem {
spacing: 4px;
i-text := Text {
text: root.item.text;
color: FluentPalette.on-surface;
font-size: FluentFontSettings.body.font-size;
font-weight: FluentFontSettings.body.font-weight;
@ -96,5 +104,5 @@ export component ListItem {
}
}
i-touch-area := TouchArea {}
@children
}

View file

@ -1,73 +0,0 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { ScrollView } from "scrollview.slint";
import { ListItem } from "components.slint";
export component ListView inherits ScrollView {
@children
}
component StandardListViewBase inherits ListView {
in property <[StandardListViewItem]> model;
in-out property <int> current-item: -1;
callback current-item-changed(/* current-item */ int);
callback item-pointer-event( /* item-index */ int, /* event */ PointerEvent, /* absolute mouse position */ Point);
public function set-current-item(index: int) {
if(index < 0 || index >= model.length) {
return;
}
current-item = index;
current-item-changed(current-item);
if(current-item-y < 0) {
self.viewport-y += 0 - current-item-y;
}
if(current-item-y + item-height > self.visible-height) {
self.viewport-y -= current-item-y + item-height - self.visible-height;
}
}
private property <length> item-height: self.viewport-height / self.model.length;
private property <length> current-item-y: self.viewport-y + current-item * item-height;
for item[index] in root.model : ListItem {
height: self.min-height;
text: item.text;
selected: index == root.current-item;
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 {
FocusScope {
x: 0;
width: 0; // Do not react on clicks
key-pressed(event) => {
if (event.text == Key.UpArrow) {
root.set-current-item(root.current-item - 1);
return accept;
} else if (event.text == Key.DownArrow) {
root.set-current-item(root.current-item + 1);
return accept;
}
reject
}
}
}

View file

@ -19,7 +19,7 @@ export { GroupBox }
import { LineEdit } from "lineedit.slint";
export { LineEdit }
import { ListView, StandardListView } from "listview.slint";
import { ListView, StandardListView } from "../common/listview.slint";
export { ListView, StandardListView }
import { ProgressIndicator } from "progressindicator.slint";

View file

@ -9,6 +9,9 @@ export { Button }
import { ScrollView } from "scrollview.slint";
export { ScrollView }
import { ListItem } from "components.slint";
export { ListItem }
import { FluentPalette, FluentFontSettings } from "styling.slint";
export global StyleMetrics {

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { FluentPalette, FluentFontSettings, Icons } from "styling.slint";
import { ListView } from "listview.slint";
import { ListView } from "../common/listview.slint";
component TableViewColumn inherits Rectangle {
in property <SortOrder> sort-order: SortOrder.unsorted;

View file

@ -1,5 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { StyleMetrics, ScrollView, Button } from "../fluent-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button }
import { StyleMetrics, ScrollView, Button, ListItem } from "../fluent-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, ListItem }

View file

@ -1,5 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { StyleMetrics, ScrollView, Button } from "../fluent-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button }
import { StyleMetrics, ScrollView, Button, ListItem } from "../fluent-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, ListItem }

View file

@ -1,5 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { StyleMetrics, ScrollView, Button } from "../fluent-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button }
import { StyleMetrics, ScrollView, Button, ListItem } from "../fluent-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, ListItem }

View file

@ -3,7 +3,7 @@
import { MaterialPalette, MaterialFontSettings, Elevation, Icons } from "styling.slint";
import { ListItem } from "components.slint";
import { ListItem, StateLayer } from "components.slint";
import { ComboBoxBase } from "../common/combobox-base.slint";
export component ComboBox {
@ -83,7 +83,7 @@ export component ComboBox {
width: root.width;
i-popup-container := Rectangle {
background: MaterialPalette.surface;
background: MaterialPalette.background-alt;
drop-shadow-color: MaterialPalette.shadow;
drop-shadow-blur: Elevation.level2;
drop-shadow-offset-y: 1px;
@ -92,11 +92,15 @@ export component ComboBox {
VerticalLayout {
for value[index] in root.model: ListItem {
text: value;
selected: index == root.current-index;
item: { text: value };
is-selected: index == root.current-index;
has-hover: i-touch-area.has-hover;
pressed: i-touch-area.pressed;
clicked => {
i-base.select(index);
i-touch-area := StateLayer {
clicked => {
i-base.select(index);
}
}
}
}

View file

@ -90,23 +90,56 @@ export component StateLayer inherits TouchArea {
}
// A selectable item that is used by `StandardListView` and `ComboBox`.
export component ListItem inherits Rectangle {
in property <bool> selected;
in property <string> text;
out property <length> mouse-x <=> i-state-layer.mouse-x;
out property <length> mouse-y <=> i-state-layer.mouse-y;
export component ListItem {
in property <StandardListViewItem> item;
in-out property <bool> is_selected;
in property <bool> has_hover;
in property <bool> has_focus;
in property <bool> pressed;
in property <int> index;
in property <length> pressed-x;
in property <length> pressed-y;
callback clicked <=> i-state-layer.clicked;
callback pointer-event <=> i-state-layer.pointer-event;
min-width: i-layout.min-width;
min-height: max(48px, i-layout.min-height);
vertical-stretch: 0;
horizontal-stretch: 1;
height: max(48px, i-layout.min-height);
states [
pressed when root.pressed: {
i-ripple.opacity: 0.12;
}
checked when root.is-selected: {
i-ripple.opacity: 1.0;
i-ripple.background: MaterialPalette.surface;
}
hover when root.has-hover: {
i-ripple.opacity: 0.08;
}
focused when root.has-focus: {
i-ripple.opacity: 0.12;
}
]
i-state-layer := StateLayer {
checked: root.selected;
i-ripple := Ripple {
opacity: 0;
active: root.pressed;
ripple-x: root.pressed-x;
ripple-y: root.pressed-y;
clip: true;
border-radius: 4px;
background: MaterialPalette.accent;
selection-background: MaterialPalette.surface;
ripple-color: MaterialPalette.accent-ripple;
has-ripple: true;
has-effect: true;
animate opacity { duration: 250ms; easing: ease; }
animate background { duration: 250ms; }
}
if (root.has-focus) : Rectangle {
border-radius: 4px;
border-width: 2px;
border-color: MaterialPalette.accent;
}
i-layout := HorizontalLayout {
@ -114,7 +147,7 @@ export component ListItem inherits Rectangle {
padding-right: 12px;
label := Text {
text: root.text;
text: root.item.text;
color: MaterialPalette.on-surface;
vertical-alignment: center;
// FIXME after Roboto font can be loaded
@ -123,4 +156,6 @@ export component ListItem inherits Rectangle {
font-weight: MaterialFontSettings.label-large.font-weight;
}
}
@children
}

View file

@ -1,74 +0,0 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { ScrollView } from "scrollview.slint";
import { ListItem } from "components.slint";
// `ListView` is like a `Scrollview` but it should have a `for` element, and the content is automatically laid out in a list.
export component ListView inherits ScrollView {
@children
}
component StandardListViewBase inherits ListView {
in property <[StandardListViewItem]> model;
in-out property <int> current-item: -1;
callback current-item-changed(/* current-item */ int);
callback item-pointer-event(/* item-index */int, /* event */ PointerEvent, /* absolute mouse position */ Point);
public function set-current-item(index: int) {
if(index < 0 || index >= model.length) {
return;
}
current-item = index;
current-item-changed(current-item);
if(current-item-y < 0) {
self.viewport-y += 0 - current-item-y;
}
if(current-item-y + item-height > self.visible-height) {
self.viewport-y -= current-item-y + item-height - self.visible-height;
}
}
private property <length> item-height: self.viewport-height / self.model.length;
private property <length> current-item-y: self.viewport-y + current-item * item-height;
for item[idx] in root.model : ListItem {
selected: idx == root.current-item;
text: item.text;
clicked => {
set-current-item(idx);
}
pointer-event(pe) => {
root.item-pointer-event(idx, 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,
});
}
}
}
// Like `ListView`, but with a default delegate, and a `model` property which is a model of type `StandardListViewItem`.
export component StandardListView inherits StandardListViewBase {
FocusScope {
x: 0;
width: 0; // Do not react on clicks
key-pressed(event) => {
if (event.text == Key.UpArrow) {
root.set-current-item(root.current-item - 1);
return accept;
} else if (event.text == Key.DownArrow) {
root.set-current-item(root.current-item + 1);
return accept;
}
reject
}
}
}

View file

@ -12,7 +12,7 @@ import { GroupBox } from "groupbox.slint";
import { VerticalBox, HorizontalBox, GridBox } from "layouts.slint";
import { Slider } from "slider.slint";
import { ComboBox } from "combobox.slint";
import { ListView, StandardListView } from "listview.slint";
import { ListView, StandardListView } from "../common/listview.slint";
import { SpinBox } from "spinbox.slint";
import { StandardTableView } from "tableview.slint";
import { ProgressIndicator } from "progressindicator.slint";
@ -23,4 +23,4 @@ export { StyleMetrics, ScrollView, Button, ComboBox, CheckBox, GroupBox, Standar
GridBox, Slider, ListView, StandardListView, StandardTableView, SpinBox, ProgressIndicator, Switch }
import { Spinner } from "spinner.slint";
export { Spinner }
export { Spinner }

View file

@ -10,6 +10,9 @@ import { Switch } from "switch.slint";
export { Button, CheckBox, ScrollView, Switch }
import { ListItem } from "components.slint";
export { ListItem }
export global StyleMetrics {
out property <length> layout-spacing: 16px;
out property <length> layout-padding: 16px;

View file

@ -1,7 +1,7 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { ScrollView } from "std-widgets-impl.slint";
import { ListView } from "../common/listview.slint";
import { StateLayer } from "components.slint";
import { MaterialPalette, Icons } from "styling.slint";
@ -201,43 +201,37 @@ export component StandardTableView {
}
}
i-scroll-view := ScrollView {
vertical-stretch: 1;
i-scroll-view := ListView {
for row[idx] in root.rows : TableViewRow {
selected: idx == root.current-row;
VerticalLayout {
alignment: start;
clicked => {
root.focus();
root.set-current-row(idx);
}
for row[idx] in root.rows : TableViewRow {
selected: idx == root.current-row;
pointer-event(pe) => {
root.row-pointer-event(idx, 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,
});
}
clicked => {
root.focus();
root.set-current-row(idx);
}
for cell[index] in row : TableViewCell {
private property <bool> has_inner_focus;
pointer-event(pe) => {
root.row-pointer-event(idx, 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,
});
}
horizontal-stretch: root.columns[index].horizontal-stretch;
min-width: max(columns[index].min-width, columns[index].width);
preferred-width: self.min-width;
max-width: (index < columns.length && columns[index].width >= 1px) ? max(columns[index].min-width, columns[index].width) : 100000px;
for cell[index] in row : TableViewCell {
private property <bool> has_inner_focus;
horizontal-stretch: root.columns[index].horizontal-stretch;
min-width: max(columns[index].min-width, columns[index].width);
preferred-width: self.min-width;
max-width: (index < columns.length && columns[index].width >= 1px) ? max(columns[index].min-width, columns[index].width) : 100000px;
Rectangle {
Text {
width: 100%;
height: 100%;
overflow: elide;
vertical-alignment: center;
text: cell.text;
}
Rectangle {
Text {
width: 100%;
height: 100%;
overflow: elide;
vertical-alignment: center;
text: cell.text;
}
}
}

View file

@ -1,5 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { StyleMetrics, ScrollView, Button } from "../material-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button }
import { StyleMetrics, ScrollView, Button, ListItem } from "../material-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, ListItem }

View file

@ -1,5 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { StyleMetrics, ScrollView, Button } from "../material-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button }
import { StyleMetrics, ScrollView, Button, ListItem } from "../material-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, ListItem }

View file

@ -1,5 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { StyleMetrics, ScrollView, Button, Switch } from "../material-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, Switch }
import { StyleMetrics, ScrollView, Button, Switch, ListItem } from "../material-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, Switch, ListItem }

View file

@ -1,71 +0,0 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
import { ScrollView } from "std-widgets-impl.slint";
export component ListView inherits ScrollView {
@children
}
component StandardListViewBase inherits ListView {
in property <[StandardListViewItem]> model;
in-out property <int> current-item: -1;
callback current-item-changed(/* current-item */ int);
callback item-pointer-event(/* item-index */int, /* event */ PointerEvent, /* absolute mouse position */ Point);
public function set-current-item(index: int) {
if(index < 0 || index >= model.length) {
return;
}
root.current-item = index;
root.current-item-changed(current-item);
if(current-item-y < 0) {
self.viewport-y += 0 - current-item-y;
}
if(current-item-y + item-height > self.visible-height) {
self.viewport-y -= current-item-y + item-height - self.visible-height;
}
}
private property <length> item-height: self.viewport-height / self.model.length;
private property <length> current-item-y: self.viewport-y + current-item * item-height;
for item[i] in root.model : NativeStandardListViewItem {
item: item;
index: i;
is-selected: root.current-item == i;
has-hover: i-touch-area.has-hover;
i-touch-area := TouchArea {
clicked => {
set-current-item(i);
}
pointer-event(pe) => {
root.item-pointer-event(i, 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 {
FocusScope {
key-pressed(event) => {
if (event.text == Key.UpArrow) {
root.set-current-item(root.current-item - 1);
return accept;
} else if (event.text == Key.DownArrow) {
root.set-current-item(root.current-item + 1);
return accept;
}
reject
}
}
}

View file

@ -5,3 +5,5 @@ export { NativeStyleMetrics as StyleMetrics }
import { ScrollView } from "scrollview.slint";
export { ScrollView }
export component ListItem inherits NativeStandardListViewItem {}

View file

@ -27,9 +27,6 @@ export { GroupBox }
import { LineEdit } from "lineedit.slint";
export { LineEdit }
import { ListView, StandardListView } from "listview.slint";
export { ListView, StandardListView }
import { ComboBox } from "combobox.slint";
export { ComboBox }
@ -43,4 +40,7 @@ import { ProgressIndicator } from "progressindicator.slint";
export { ProgressIndicator }
import { Spinner } from "spinner.slint";
export { Spinner }
export { Spinner }
import { StandardListView, ListView } from "../common/listview.slint";
export { StandardListView, ListView }