ContextMenu on the LineEdit and TextEdit

With usual copy/paste entries

We need to make sure that showing the popup window don't clear the
selection in the TextInput which happens if the TextInput gets a
FocusOut event.
This commit is contained in:
Olivier Goffart 2025-02-10 17:36:40 +01:00
parent 9195f3e265
commit c85b20d431
7 changed files with 223 additions and 139 deletions

View file

@ -67,38 +67,57 @@ export component LineEditBase inherits Rectangle {
accessible-role: none;
}
text-input := TextInput {
property <length> computed-x;
ContextMenu {
MenuItem {
title: @tr("Cut");
activated => { text-input.cut(); }
}
MenuItem {
title: @tr("Copy");
activated => { text-input.copy(); }
}
MenuItem {
title: @tr("Paste");
activated => { text-input.paste(); }
}
MenuItem {
title: @tr("Select All");
activated => { text-input.select-all(); }
}
x: min(0px, max(parent.width - self.width - self.text-cursor-width, self.computed-x));
width: max(parent.width - self.text-cursor-width, self.preferred-width);
height: 100%;
vertical-alignment: center;
single-line: true;
color: root.text-color;
text-input := TextInput {
property <length> computed-x;
cursor-position-changed(cursor-position) => {
if cursor-position.x + self.computed_x < root.margin {
self.computed_x = - cursor-position.x + root.margin;
} else if cursor-position.x + self.computed_x > parent.width - root.margin - self.text-cursor-width {
self.computed_x = parent.width - cursor-position.x - root.margin - self.text-cursor-width;
x: min(0px, max(parent.width - self.width - self.text-cursor-width, self.computed-x));
width: max(parent.width - self.text-cursor-width, self.preferred-width);
height: 100%;
vertical-alignment: center;
single-line: true;
color: root.text-color;
cursor-position-changed(cursor-position) => {
if cursor-position.x + self.computed_x < root.margin {
self.computed_x = - cursor-position.x + root.margin;
} else if cursor-position.x + self.computed_x > parent.width - root.margin - self.text-cursor-width {
self.computed_x = parent.width - cursor-position.x - root.margin - self.text-cursor-width;
}
}
}
accepted => {
root.accepted(self.text);
}
accepted => {
root.accepted(self.text);
}
edited => {
root.edited(self.text);
}
edited => {
root.edited(self.text);
}
key-pressed(event) => {
root.key-pressed(event)
}
key-pressed(event) => {
root.key-pressed(event)
}
key-released(event) => {
root.key-released(event)
key-released(event) => {
root.key-released(event)
}
}
}
}

View file

@ -58,48 +58,68 @@ export component TextEditBase inherits Rectangle {
forward-focus: text-input;
scroll-view := ScrollView {
x: root.scroll-view-padding;
y: root.scroll-view-padding;
width: parent.width - 2 * root.scroll-view-padding;
height: parent.height - 2 * root.scroll-view-padding;
viewport-width: root.wrap == TextWrap.no-wrap ? max(self.visible-width, text-input.preferred-width) : self.visible-width;
viewport-height: max(self.visible-height, text-input.preferred-height);
ContextMenu {
MenuItem {
title: @tr("Cut");
activated => { text-input.cut(); }
}
MenuItem {
title: @tr("Copy");
activated => { text-input.copy(); }
}
MenuItem {
title: @tr("Paste");
activated => { text-input.paste(); }
}
MenuItem {
title: @tr("Select All");
activated => { text-input.select-all(); }
}
text-input := TextInput {
enabled: true;
single-line: false;
wrap: word-wrap;
selection-background-color: root.selection-background-color;
selection-foreground-color: root.selection-foreground-color;
page-height: scroll-view.visible-height;
scroll-view := ScrollView {
x: root.scroll-view-padding;
y: root.scroll-view-padding;
width: parent.width - 2 * root.scroll-view-padding;
height: parent.height - 2 * root.scroll-view-padding;
viewport-width: root.wrap == TextWrap.no-wrap ? max(self.visible-width, text-input.preferred-width) : self.visible-width;
viewport-height: max(self.visible-height, text-input.preferred-height);
edited => {
root.edited(self.text);
}
text-input := TextInput {
enabled: true;
single-line: false;
wrap: word-wrap;
selection-background-color: root.selection-background-color;
selection-foreground-color: root.selection-foreground-color;
page-height: scroll-view.visible-height;
key-pressed(event) => {
root.key-pressed(event)
}
key-released(event) => {
root.key-released(event)
}
cursor-position-changed(cpos) => {
if (cpos.x + root.viewport-x < 12px) {
root.viewport-x = min(0px, max(parent.visible-width - self.width, - cpos.x + 12px));
} else if (cpos.x + root.viewport-x > parent.visible-width - 12px) {
root.viewport-x = min(0px, max(parent.visible-width - self.width, parent.visible-width - cpos.x - 12px));
edited => {
root.edited(self.text);
}
if (cpos.y + root.viewport-y < 12px) {
root.viewport-y = min(0px, max(parent.visible-height - self.height, - cpos.y + 12px));
} else if (cpos.y + root.viewport-y > parent.visible-height - 12px - 20px) {
// FIXME: font-height hardcoded to 20px
root.viewport-y = min(0px, max(parent.visible-height - self.height, parent.visible-height - cpos.y - 12px - 20px));
key-pressed(event) => {
root.key-pressed(event)
}
key-released(event) => {
root.key-released(event)
}
cursor-position-changed(cpos) => {
if (cpos.x + root.viewport-x < 12px) {
root.viewport-x = min(0px, max(parent.visible-width - self.width, - cpos.x + 12px));
} else if (cpos.x + root.viewport-x > parent.visible-width - 12px) {
root.viewport-x = min(0px, max(parent.visible-width - self.width, parent.visible-width - cpos.x - 12px));
}
if (cpos.y + root.viewport-y < 12px) {
root.viewport-y = min(0px, max(parent.visible-height - self.height, - cpos.y + 12px));
} else if (cpos.y + root.viewport-y > parent.visible-height - 12px - 20px) {
// FIXME: font-height hardcoded to 20px
root.viewport-y = min(0px, max(parent.visible-height - self.height, parent.visible-height - cpos.y - 12px - 20px));
}
}
}
}
}
placeholder := Text {

View file

@ -138,48 +138,67 @@ export component TextEdit {
border-width: 1px;
}
scroll-view := ScrollView {
x: 8px;
y: 8px;
width: parent.width - 16px;
height: parent.height - 16px;
viewport-width: root.wrap == TextWrap.no-wrap ? max(self.visible-width, text-input.preferred-width) : self.visible-width;
viewport-height: max(self.visible-height, text-input.preferred-height);
ContextMenu {
MenuItem {
title: @tr("Cut");
activated => { text-input.cut(); }
}
MenuItem {
title: @tr("Copy");
activated => { text-input.copy(); }
}
MenuItem {
title: @tr("Paste");
activated => { text-input.paste(); }
}
MenuItem {
title: @tr("Select All");
activated => { text-input.select-all(); }
}
text-input := TextInput {
enabled: true;
color: CupertinoPalette.foreground;
font-size: CupertinoFontSettings.body.font-size;
font-weight: CupertinoFontSettings.body.font-weight;
selection-background-color: CupertinoPalette.selection-background;
selection-foreground-color: self.color;
single-line: false;
wrap: word-wrap;
page-height: scroll-view.visible-height;
scroll-view := ScrollView {
x: 8px;
y: 8px;
width: parent.width - 16px;
height: parent.height - 16px;
viewport-width: root.wrap == TextWrap.no-wrap ? max(self.visible-width, text-input.preferred-width) : self.visible-width;
viewport-height: max(self.visible-height, text-input.preferred-height);
edited => {
root.edited(self.text);
}
text-input := TextInput {
enabled: true;
color: CupertinoPalette.foreground;
font-size: CupertinoFontSettings.body.font-size;
font-weight: CupertinoFontSettings.body.font-weight;
selection-background-color: CupertinoPalette.selection-background;
selection-foreground-color: self.color;
single-line: false;
wrap: word-wrap;
page-height: scroll-view.visible-height;
key-pressed(event) => {
root.key-pressed(event)
}
key-released(event) => {
root.key-released(event)
}
cursor-position-changed(cpos) => {
if (cpos.x + root.viewport-x < 12px) {
root.viewport-x = min(0px, max(parent.visible-width - self.width, - cpos.x + 12px));
} else if (cpos.x + root.viewport-x > parent.visible-width - 12px) {
root.viewport-x = min(0px, max(parent.visible-width - self.width, parent.visible-width - cpos.x - 12px));
edited => {
root.edited(self.text);
}
if (cpos.y + root.viewport-y < 12px) {
root.viewport-y = min(0px, max(parent.visible-height - self.height, - cpos.y + 12px));
} else if (cpos.y + root.viewport-y > parent.visible-height - 12px - 20px) {
// FIXME: font-height hardcoded to 20px
root.viewport-y = min(0px, max(parent.visible-height - self.height, parent.visible-height - cpos.y - 12px - 20px));
key-pressed(event) => {
root.key-pressed(event)
}
key-released(event) => {
root.key-released(event)
}
cursor-position-changed(cpos) => {
if (cpos.x + root.viewport-x < 12px) {
root.viewport-x = min(0px, max(parent.visible-width - self.width, - cpos.x + 12px));
} else if (cpos.x + root.viewport-x > parent.visible-width - 12px) {
root.viewport-x = min(0px, max(parent.visible-width - self.width, parent.visible-width - cpos.x - 12px));
}
if (cpos.y + root.viewport-y < 12px) {
root.viewport-y = min(0px, max(parent.visible-height - self.height, - cpos.y + 12px));
} else if (cpos.y + root.viewport-y > parent.visible-height - 12px - 20px) {
// FIXME: font-height hardcoded to 20px
root.viewport-y = min(0px, max(parent.visible-height - self.height, parent.visible-height - cpos.y - 12px - 20px));
}
}
}
}