mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 18:58:36 +00:00
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:
parent
f25a419009
commit
6d6b18300a
34 changed files with 337 additions and 403 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
137
internal/compiler/widgets/common/listview.slint
Normal file
137
internal/compiler/widgets/common/listview.slint
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,3 +5,5 @@ export { NativeStyleMetrics as StyleMetrics }
|
|||
|
||||
import { ScrollView } from "scrollview.slint";
|
||||
export { ScrollView }
|
||||
|
||||
export component ListItem inherits NativeStandardListViewItem {}
|
|
@ -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 }
|
Loading…
Add table
Add a link
Reference in a new issue