slint/internal/compiler/widgets/native/std-widgets.slint
Simon Hausmann c428601370
Add support for select-all(), cut(), copy() and paste() functions on text input elements (#2804)
In the compiler this is still very primitive, but an attempt to start a
generic interface. The basic assumption is that all item functions will
eventually need access to the window adapter and itemrc. Support for
additional arguments is still missing.

Also missing is support for the function access via rtti in the
interpreter, hence the hardcoding at the moment.
2023-06-01 16:04:53 +02:00

452 lines
14 KiB
Text

// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
// cSpell: ignore combobox spinbox
import { LineEditInner, TextEdit, AboutSlint } from "../common/common.slint";
import { StyleMetrics, ScrollView } from "std-widgets-impl.slint";
export { StyleMetrics, ScrollView, TextEdit, AboutSlint }
export component Button {
in property<string> text <=> native.text;
out property<bool> has-focus <=> native.has-focus;
out property<bool> pressed <=> native.pressed;
in property<bool> enabled <=> native.enabled;
in property<bool> checkable <=> native.checkable;
in-out property<bool> checked <=> native.checked;
in property<image> icon <=> native.icon;
callback clicked <=> native.clicked;
accessible-checkable: root.checkable;
accessible-checked: root.checked;
accessible-label: root.text;
accessible-role: button;
HorizontalLayout {
native := NativeButton {
checkable: false;
enabled: true;
}
}
}
export component StandardButton {
in property<StandardButtonKind> kind <=> native.standard-button-kind;
out property<bool> has-focus <=> native.has-focus;
out property<bool> pressed <=> native.pressed;
in property<bool> enabled <=> native.enabled;
callback clicked <=> native.clicked;
accessible-label: native.text;
accessible-role: button;
HorizontalLayout {
native := NativeButton {
is-standard-button: true;
checkable: false;
}
}
}
export component CheckBox inherits NativeCheckBox {
accessible-checkable: true;
accessible-checked <=> root.checked;
accessible-label <=> root.text;
accessible-role: checkbox;
}
export component SpinBox inherits NativeSpinBox {
accessible-role: spinbox;
accessible-value: root.value;
accessible-value-minimum: root.minimum;
accessible-value-maximum: root.maximum;
accessible-value-step: (root.maximum - root.minimum) / 100;
}
export component Slider inherits NativeSlider {
accessible-role: slider;
accessible-value: root.value;
accessible-value-minimum: root.minimum;
accessible-value-maximum: root.maximum;
accessible-value-step: (root.maximum - root.minimum) / 100;
out property <bool> has-focus: fs.has-focus;
fs := FocusScope {
x:0;
width: 0px;
key-pressed(event) => {
if (root.enabled && event.text == Key.RightArrow) {
root.value = Math.min(root.value + 1, root.maximum);
accept
} else if (root.enabled && event.text == Key.LeftArrow) {
root.value = Math.max(root.value - 1, root.minimum);
accept
} else {
reject
}
}
}
}
export component Switch inherits NativeSwitch {
accessible-checkable: true;
accessible-checked <=> root.checked;
accessible-label <=> root.text;
accessible-role: checkbox;
}
export component GroupBox {
in property title <=> native.title;
in property enabled <=> native.enabled;
native := NativeGroupBox {
GridLayout {
padding-left: native.native-padding-left;
padding-right: native.native-padding-right;
padding-top: native.native-padding-top;
padding-bottom: native.native-padding-bottom;
@children
}
}
}
export component LineEdit {
in property <length> font-size <=> inner.font-size;
in-out property <string> text <=> inner.text;
in property <string> placeholder-text <=> inner.placeholder-text;
in property input-type <=> inner.input-type;
in property horizontal-alignment <=> inner.horizontal-alignment;
in property read-only <=> inner.read-only;
out property<bool> has-focus <=> inner.has-focus;
in property<bool> enabled: true;
forward-focus: inner;
callback accepted <=> inner.accepted;
callback edited <=> inner.edited;
horizontal-stretch: 1;
vertical-stretch: 0;
public function select-all() {
inner.select-all();
}
public function cut() {
inner.cut();
}
public function copy() {
inner.copy();
}
public function paste() {
inner.paste();
}
native := NativeLineEdit {
has-focus <=> root.has-focus;
enabled: root.enabled;
width: 100%;
height: 100%;
}
HorizontalLayout {
padding-left: native.native-padding-left;
padding-right: native.native-padding-right;
padding-top: native.native-padding-top;
padding-bottom: native.native-padding-bottom;
inner := LineEditInner {
placeholder-color: self.enabled ? StyleMetrics.placeholder-color : StyleMetrics.placeholder-color-disabled;
enabled: root.enabled;
}
}
}
export component ListView inherits ScrollView {
@children
}
component StandardListViewBase inherits ListView {
private property <length> item-height: self.viewport-height / self.model.length;
private property <length> current-item-y: self.viewport-y + current-item * item-height;
in property<[StandardListViewItem]> model;
in-out property<int> current-item: -1;
for item[i] in root.model : NativeStandardListViewItem {
item: item;
index: i;
is-selected: root.current-item == i;
has-hover: ta.has-hover;
ta := TouchArea {
clicked => {
set-current-item(i);
}
}
}
public function set-current-item(index: int) {
if(index < 0 || index >= model.length) {
return;
}
root.current-item = index;
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;
}
}
}
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
}
}
}
export component ComboBox inherits NativeComboBox {
in property <[string]> model;
in-out property <int> current-index : -1;
out property has-focus <=> fs.has-focus;
enabled: true;
callback selected(string);
forward-focus: fs;
accessible-role: combobox;
accessible-value <=> root.current-value;
popup := PopupWindow {
x:0;
Rectangle { background: NativeStyleMetrics.window-background; }
NativeComboBoxPopup {
width: 100%;
height: 100%;
}
y: root.height;
width: root.width;
VerticalLayout {
spacing: 0px;
for value[i] in root.model: NativeStandardListViewItem {
item: { text: value };
is-selected: root.current-index == i;
has-hover: ta.has-hover;
ta := TouchArea {
clicked => {
if (root.enabled) {
root.current-index = i;
root.current-value = value;
root.selected(root.current-value);
}
//is-open = false;
}
}
}
}
}
fs := FocusScope {
key-pressed(event) => {
if (event.text == Key.UpArrow) {
root.current-index = Math.max(root.current-index - 1, 0);
root.current-value = root.model[root.current-index];
return accept;
} else if (event.text == Key.DownArrow) {
root.current-index = Math.min(root.current-index + 1, root.model.length - 1);
root.current-value = root.model[root.current-index];
return accept;
// PopupWindow can not get hidden again at this time, so do not allow to pop that up.
// } else if (event.text == Key.Return) {
// touch.clicked()
// return accept;
}
return reject;
}
touch := TouchArea {
enabled <=> root.enabled;
clicked => {
root.focus();
popup.show();
}
}
}
}
export component TabWidgetImpl inherits NativeTabWidget { }
export component TabImpl inherits NativeTab {
accessible-role: tab;
accessible-label <=> root.title;
}
export component TabBarImpl {
// injected properties:
in-out property<int> current; // The currently selected tab
in-out property<int> current-focused: fs.has-focus ? root.current : -1; // The currently focused tab
in-out property<int> num-tabs; // The total number of tabs
accessible-role: tab;
accessible-delegate-focus: root.current;
Rectangle {
clip: true; // The breeze style draws outside of the tab bar, which is clip by default with Qt
HorizontalLayout {
spacing: 0px; // Qt renders Tabs next to each other and renders "spacing" as part of the tab itself
alignment: NativeStyleMetrics.tab-bar-alignment;
@children
}
}
fs := FocusScope {
x:0;
width: 0px; // Do not react on clicks
key-pressed(event) => {
if (event.text == Key.LeftArrow) {
root.current = Math.max(root.current - 1, 0);
return accept;
}
if (event.text == Key.RightArrow) {
root.current = Math.min(root.current + 1, root.num-tabs - 1);
return accept;
}
return reject;
}
}
}
export component TabWidget inherits TabWidget {}
export component VerticalBox inherits VerticalLayout {
spacing: NativeStyleMetrics.layout-spacing;
padding: NativeStyleMetrics.layout-spacing;
}
export component HorizontalBox inherits HorizontalLayout {
spacing: NativeStyleMetrics.layout-spacing;
padding: NativeStyleMetrics.layout-spacing;
}
export component GridBox inherits GridLayout {
spacing: NativeStyleMetrics.layout-spacing;
padding: NativeStyleMetrics.layout-spacing;
}
export component StandardTableView {
callback sort-ascending(int);
callback sort-descending(int);
out property <int> current-sort-column: -1;
in-out property <[TableColumn]> columns;
in property <[[StandardListViewItem]]> rows;
horizontal-stretch: 1;
vertical-stretch: 1;
function sort(index: int) {
if (current-sort-column != index) {
columns[current-sort-column].sort-order = SortOrder.unsorted;
}
if(columns[index].sort-order == SortOrder.ascending) {
columns[index].sort-order = SortOrder.descending;
sort-descending(index);
} else {
columns[index].sort-order = SortOrder.ascending;
sort-ascending(index);
}
current-sort-column = index;
}
scroll-view := NativeScrollView {
vertical-max: fli.viewport-height > fli.height ? fli.viewport-height - fli.height : 0phx;
vertical-page-size: fli.height;
horizontal-max: fli.viewport-width > fli.width ? fli.viewport-width - fli.width : 0phx;
horizontal-page-size: fli.width;
fli := Flickable {
x: scroll-view.native-padding-left;
width: scroll-view.width - scroll-view.native-padding-left - scroll-view.native-padding-right;
y: scroll-view.native-padding-top + header.height;
height: scroll-view.height - self.y - scroll-view.native-padding-bottom;
interactive: false;
viewport-y <=> scroll-view.vertical-value;
viewport-x <=> scroll-view.horizontal-value;
VerticalLayout {
alignment: start;
for row[i] in rows : Rectangle {
width: max(row-layout.preferred-width, fli.width);
row-ta := TouchArea {}
row-layout := HorizontalLayout {
for cell[index] in row : Rectangle {
horizontal-stretch: 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;
HorizontalLayout {
NativeStandardListViewItem {
item: cell;
index: i;
//is-selected: root.current-item == i;
has-hover: row-ta.has-hover;
}
}
}
}
}
}
}
}
header := Rectangle {
clip: true;
height: header-layout.preferred-height;
x: scroll-view.native-padding-left;
y: scroll-view.native-padding-top;
width: fli.width;
header-layout := HorizontalLayout {
width: max(self.preferred-width, parent.width);
x: fli.viewport-x;
for column[index] in columns : NativeTableHeaderSection {
item: column;
horizontal-stretch: column.horizontal-stretch;
min-width: max(column.min-width, column.width);
preferred-width: self.min-width;
max-width: (index < columns.length && column.width >= 1px) ? max(column.min-width, column.width) : 100000px;
TouchArea {
clicked => {
sort(index);
}
}
TouchArea {
width: 10px;
x: parent.width - self.width / 2;
moved => {
if (self.pressed) {
column.width = max(1px, parent.width + (self.mouse-x - self.pressed-x));
}
}
mouse-cursor: ew-resize;
}
}
}
}
}
export component ProgressIndicator inherits NativeProgressIndicator {}