processEvent
Parameters
- self:*TextEntryWidget
- *TextEntryWidget
- e:*Event
- *Event
Source
Implementation
pub fn processEvent(self: *TextEntryWidget, e: *Event) void {
// scroll gets first crack, because it is logically outside the text area
self.scroll.scroll.?.processEvent(e);
if (e.handled) return;
switch (e.evt) {
.key => |ke| blk: {
if ((ke.action == .down or ke.action == .repeat) and ke.matchBind("next_widget")) {
e.handle(@src(), self.data());
dvui.tabIndexNext(e.num);
break :blk;
}
if ((ke.action == .down or ke.action == .repeat) and ke.matchBind("prev_widget")) {
e.handle(@src(), self.data());
dvui.tabIndexPrev(e.num);
break :blk;
}
if (ke.action == .down and ke.matchBind("paste")) {
e.handle(@src(), self.data());
self.paste();
break :blk;
}
if (ke.action == .down and ke.matchBind("cut")) {
e.handle(@src(), self.data());
self.cut();
break :blk;
}
if (ke.action == .down and ke.matchBind("copy")) {
e.handle(@src(), self.data());
self.copy();
break :blk;
}
if (ke.action == .down and ke.matchBind("text_start")) {
e.handle(@src(), self.data());
self.textLayout.selection.moveCursor(0, false);
self.textLayout.scroll_to_cursor = true;
break :blk;
}
if (ke.action == .down and ke.matchBind("text_end")) {
e.handle(@src(), self.data());
self.textLayout.selection.moveCursor(std.math.maxInt(usize), false);
self.textLayout.scroll_to_cursor = true;
break :blk;
}
if (ke.action == .down and ke.matchBind("line_start")) {
e.handle(@src(), self.data());
if (self.textLayout.sel_move == .none) {
self.textLayout.sel_move = .{ .expand_pt = .{ .select = false, .which = .home } };
}
break :blk;
}
if (ke.action == .down and ke.matchBind("line_end")) {
e.handle(@src(), self.data());
if (self.textLayout.sel_move == .none) {
self.textLayout.sel_move = .{ .expand_pt = .{ .select = false, .which = .end } };
}
break :blk;
}
if ((ke.action == .down or ke.action == .repeat) and ke.matchBind("word_left")) {
e.handle(@src(), self.data());
if (!self.textLayout.selection.empty()) {
self.textLayout.selection.moveCursor(self.textLayout.selection.start, false);
} else {
if (self.textLayout.sel_move == .none) {
self.textLayout.sel_move = .{ .word_left_right = .{ .select = false } };
}
if (self.textLayout.sel_move == .word_left_right) {
self.textLayout.sel_move.word_left_right.count -= 1;
}
}
break :blk;
}
if ((ke.action == .down or ke.action == .repeat) and ke.matchBind("word_right")) {
e.handle(@src(), self.data());
if (!self.textLayout.selection.empty()) {
self.textLayout.selection.moveCursor(self.textLayout.selection.end, false);
self.textLayout.selection.affinity = .before;
} else {
if (self.textLayout.sel_move == .none) {
self.textLayout.sel_move = .{ .word_left_right = .{ .select = false } };
}
if (self.textLayout.sel_move == .word_left_right) {
self.textLayout.sel_move.word_left_right.count += 1;
}
}
break :blk;
}
if ((ke.action == .down or ke.action == .repeat) and ke.matchBind("char_left")) {
e.handle(@src(), self.data());
if (!self.textLayout.selection.empty()) {
self.textLayout.selection.moveCursor(self.textLayout.selection.start, false);
} else {
if (self.textLayout.sel_move == .none) {
self.textLayout.sel_move = .{ .char_left_right = .{ .select = false } };
}
if (self.textLayout.sel_move == .char_left_right) {
self.textLayout.sel_move.char_left_right.count -= 1;
}
}
break :blk;
}
if ((ke.action == .down or ke.action == .repeat) and ke.matchBind("char_right")) {
e.handle(@src(), self.data());
if (!self.textLayout.selection.empty()) {
self.textLayout.selection.moveCursor(self.textLayout.selection.end, false);
self.textLayout.selection.affinity = .before;
} else {
if (self.textLayout.sel_move == .none) {
self.textLayout.sel_move = .{ .char_left_right = .{ .select = false } };
}
if (self.textLayout.sel_move == .char_left_right) {
self.textLayout.sel_move.char_left_right.count += 1;
}
}
break :blk;
}
if ((ke.action == .down or ke.action == .repeat) and ke.matchBind("char_up")) {
e.handle(@src(), self.data());
if (self.textLayout.sel_move == .none) {
self.textLayout.sel_move = .{ .cursor_updown = .{ .select = false } };
}
if (self.textLayout.sel_move == .cursor_updown) {
self.textLayout.sel_move.cursor_updown.count -= 1;
}
break :blk;
}
if ((ke.action == .down or ke.action == .repeat) and ke.matchBind("char_down")) {
e.handle(@src(), self.data());
if (self.textLayout.sel_move == .none) {
self.textLayout.sel_move = .{ .cursor_updown = .{ .select = false } };
}
if (self.textLayout.sel_move == .cursor_updown) {
self.textLayout.sel_move.cursor_updown.count += 1;
}
break :blk;
}
switch (ke.code) {
.backspace => {
if (ke.action == .down or ke.action == .repeat) {
e.handle(@src(), self.data());
var sel = self.textLayout.selectionGet(self.len);
if (!sel.empty()) {
// just delete selection
self.textChangedRemoved(sel.start, sel.end);
@memmove(self.text[sel.start..][0 .. self.len - sel.end], self.text[sel.end..self.len]);
self.setLen(self.len - (sel.end - sel.start));
sel.end = sel.start;
sel.cursor = sel.start;
self.textLayout.scroll_to_cursor = true;
} else if (ke.matchBind("delete_prev_word")) {
// delete word before cursor
const oldcur = sel.cursor;
// find end of last word
if (sel.cursor > 0 and std.mem.findAny(u8, self.text[sel.cursor - 1 ..][0..1], " \n") != null) {
sel.cursor = std.mem.findLastNone(u8, self.text[0..sel.cursor], " \n") orelse 0;
}
// find start of word
if (std.mem.findLastAny(u8, self.text[0..sel.cursor], " \n")) |last_space| {
sel.cursor = last_space + 1;
} else {
sel.cursor = 0;
}
// delete from sel.cursor to oldcur
if (sel.cursor != oldcur) self.textChangedRemoved(sel.cursor, oldcur);
@memmove(self.text[sel.cursor..][0 .. self.len - oldcur], self.text[oldcur..self.len]);
self.setLen(self.len - (oldcur - sel.cursor));
sel.end = sel.cursor;
sel.start = sel.cursor;
self.textLayout.scroll_to_cursor = true;
} else if (sel.cursor > 0) {
// delete character just before cursor
//
// A utf8 char might consist of more than one byte.
// Find the beginning of the last byte by iterating over
// the string backwards. The first byte of a utf8 char
// does not have the pattern 10xxxxxx.
var i: usize = 1;
while (sel.cursor - i > 0 and self.text[sel.cursor - i] & 0xc0 == 0x80) : (i += 1) {}
self.textChangedRemoved(sel.cursor - i, sel.cursor);
@memmove(self.text[sel.cursor - i ..][0 .. self.len - sel.cursor], self.text[sel.cursor..self.len]);
self.setLen(self.len - i);
sel.cursor -= i;
sel.start = sel.cursor;
sel.end = sel.cursor;
self.textLayout.scroll_to_cursor = true;
}
}
},
.delete => {
if (ke.action == .down or ke.action == .repeat) {
e.handle(@src(), self.data());
var sel = self.textLayout.selectionGet(self.len);
if (!sel.empty()) {
// just delete selection
self.textChangedRemoved(sel.start, sel.end);
@memmove(self.text[sel.start..][0 .. self.len - sel.end], self.text[sel.end..self.len]);
self.setLen(self.len - (sel.end - sel.start));
sel.end = sel.start;
sel.cursor = sel.start;
self.textLayout.scroll_to_cursor = true;
} else if (ke.matchBind("delete_next_word")) {
// delete word after cursor
const oldcur = sel.cursor;
// find start of next word
if (sel.cursor < self.len and std.mem.findAny(u8, self.text[sel.cursor..][0..1], " \n") != null) {
sel.cursor = std.mem.findNonePos(u8, self.text, sel.cursor, " \n") orelse self.len;
}
// find end of word
if (std.mem.findAny(u8, self.text[sel.cursor..self.len], " \n")) |last_space| {
sel.cursor = sel.cursor + last_space;
} else {
sel.cursor = self.len;
}
// delete from oldcur to sel.cursor
if (sel.cursor != oldcur) self.textChangedRemoved(oldcur, sel.cursor);
@memmove(self.text[oldcur..][0 .. self.len - sel.cursor], self.text[sel.cursor..self.len]);
self.setLen(self.len - (sel.cursor - oldcur));
sel.cursor = oldcur;
sel.end = sel.cursor;
sel.start = sel.cursor;
self.textLayout.scroll_to_cursor = true;
} else if (sel.cursor < self.len) {
// delete the character just after the cursor
//
// A utf8 char might consist of more than one byte.
const ii = std.unicode.utf8ByteSequenceLength(self.text[sel.cursor]) catch 1;
const i = @min(ii, self.len - sel.cursor);
self.textChangedRemoved(sel.cursor, sel.cursor + i);
const remaining = self.len - (sel.cursor + i);
@memmove(self.text[sel.cursor..][0..remaining], self.text[sel.cursor + i ..][0..remaining]);
self.setLen(self.len - i);
self.textLayout.scroll_to_cursor = true;
}
}
},
.enter => {
if (ke.action == .down or ke.action == .repeat) {
e.handle(@src(), self.data());
if (self.init_opts.multiline) {
self.textTyped("\n", false);
} else if (ke.action == .down) {
self.enter_pressed = true;
dvui.refresh(null, @src(), self.data().id);
}
}
},
else => {},
}
},
.text => |te| {
switch (te.action) {
.value => |set| {
e.handle(@src(), self.data());
var new = std.mem.sliceTo(set.txt, 0);
if (self.init_opts.multiline) {
self.textTyped(new, set.selected);
} else {
var i: usize = 0;
while (i < new.len) {
if (std.mem.findScalar(u8, new[i..], '\n')) |idx| {
self.textTyped(new[i..][0..idx], set.selected);
i += idx + 1;
} else {
self.textTyped(new[i..], set.selected);
break;
}
}
}
},
else => {},
}
},
.mouse => |me| {
if (me.action == .focus) {
e.handle(@src(), self.data());
dvui.focusWidget(self.data().id, null, e.num);
}
},
else => {},
}
if (!e.handled) {
self.textLayout.processEvent(e);
if (!e.handled and e.evt == .key) {
switch (e.evt.key.code) {
.page_up, .page_down => {}, // handled by scroll container
else => {
// Mark all remaining key events as handled. This allows
// checking a keybind (like "d") after the textEntry, but
// where textEntry will get it first.
e.handle(@src(), self.data());
},
}
}
}
}