EditBox caret keys, selection and focus blink follow 1.12.1 (#750)
Several EditBox keys differed from 1.12.1: - Up/Down did nothing in a multi-line box; - Home/End went to the text's ends; - `HighlightText` and Ctrl+A moved the caret; - focus did not restart the blink; - a non-digit kept a numeric box's selection; - Option+Up could not recall chat history on a Mac. In 1.12.1, Up/Down move a wrapped row, keeping the letter column (`0x77cb20`), or recall history in a single-line box with any modifier but Shift. Home/End stop at a newline (`0x77c980`, `0x77c9f0`). `HighlightText` writes only the selection (`0x77cca0`), and Shift extends by `0x77cd10`. Focus restarts the blink (`0x77dd68`), and Insert deletes the selection before its digit test (`0x77bf41`). benilla now does each. Down onto the last row lands at the end, and history stays shell-style, both deviations. Tests cover each key, in the engine and the stock mail and ticket boxes. Fixes #735 Fixes #736 Fixes #737 Fixes #738 Fixes #745 Fixes #747 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
be9e3d8aac
commit
f22ec0016c
@@ -244,7 +244,11 @@ fn chat_box_arrows_edit_and_history_recalls() {
|
||||
);
|
||||
assert!(s.focus_editbox("ChatFrameEditBox"));
|
||||
s.char_input("dra");
|
||||
s.editbox_action(EditAction::HistoryPrev);
|
||||
s.editbox_action(EditAction::Move {
|
||||
unit: EditUnit::Row,
|
||||
back: true,
|
||||
extend: false,
|
||||
});
|
||||
// `ChatEdit_AddHistory` filed "/y hi" (`ChatFrame.lua:1916-1937`); the recall's `OnTextSet`
|
||||
// parse (l.2077-2079) turns it back into YELL with "hi".
|
||||
assert!(
|
||||
@@ -258,7 +262,11 @@ fn chat_box_arrows_edit_and_history_recalls() {
|
||||
s.eval::<String>("return ChatFrameEditBox:GetText()")
|
||||
.unwrap()
|
||||
);
|
||||
s.editbox_action(EditAction::HistoryNext);
|
||||
s.editbox_action(EditAction::Move {
|
||||
unit: EditUnit::Row,
|
||||
back: false,
|
||||
extend: false,
|
||||
});
|
||||
assert_eq!(
|
||||
s.eval::<String>("return ChatFrameEditBox:GetText()")
|
||||
.unwrap(),
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
//! The stock GM help window (`HelpFrame.xml`): the two faces of `UPDATE_TICKET`, the ticket
|
||||
//! toast and its repoll, and the status ask on show.
|
||||
|
||||
use benilla_ui::script::{GmTicketIntent, ScriptValue, UiScript};
|
||||
use benilla_ui::script::{
|
||||
EditAction, EditBoxAdvanceRequest, EditUnit, GmTicketIntent, MeasureRequest, ScriptValue,
|
||||
TextMeasure, UiScript,
|
||||
};
|
||||
|
||||
use super::test_ui::load_ui as load_xml;
|
||||
|
||||
@@ -237,3 +240,62 @@ fn the_ticket_text_grows_with_its_lines_and_its_scroll_frame_ranges_over_them()
|
||||
);
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
/// A stand-in font engine: each byte 7 wide, each line 14 tall, a multi-line box's rows starting
|
||||
/// after each newline, a trailing one opening an empty row as the reference's break routine does
|
||||
/// (`0x5c250b`–`0x5c2522`); the texts here never wrap.
|
||||
struct Rows;
|
||||
|
||||
impl TextMeasure for Rows {
|
||||
fn measure(&mut self, req: &MeasureRequest) -> (f32, f32, f32) {
|
||||
let natural = req.text.split('\n').map(str::len).max().unwrap_or(0) as f32 * 7.0;
|
||||
(natural, req.text.split('\n').count() as f32 * 14.0, natural)
|
||||
}
|
||||
|
||||
fn editbox_advances(
|
||||
&mut self,
|
||||
req: &EditBoxAdvanceRequest,
|
||||
) -> Option<(Vec<f32>, Vec<usize>, f32)> {
|
||||
let cum = (0..=req.text.len()).map(|i| i as f32 * 7.0).collect();
|
||||
if req.wrap_width.is_none() {
|
||||
return Some((cum, vec![0], 0.0));
|
||||
}
|
||||
let breaks = req.text.match_indices('\n').map(|(i, _)| i + 1);
|
||||
let rows = std::iter::once(0).chain(breaks).collect();
|
||||
Some((cum, rows, 14.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// The ticket text is multi-line: UP moves the caret a row at its letter column (`0x77cb20`), and
|
||||
/// Shift+HOME selects back to the line's start (`0x77c980`), not the ticket's.
|
||||
#[test]
|
||||
fn the_ticket_texts_caret_moves_by_rows_and_lines() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = setup();
|
||||
s.set_text_measurer(Box::new(Rows));
|
||||
s.run("ShowUIPanel(HelpFrame) HelpFrame_ShowFrame(\"OpenTicket\")")
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
s.run(
|
||||
"HelpFrameOpenTicketText:SetText('Stuck in\\nthe mine') \
|
||||
HelpFrameOpenTicketText:SetFocus()",
|
||||
)
|
||||
.unwrap();
|
||||
s.tick(0.016);
|
||||
s.resolve();
|
||||
let mv = |s: &mut UiScript, unit, back, extend| {
|
||||
assert!(s.editbox_action(EditAction::Move { unit, back, extend }));
|
||||
};
|
||||
// From the end, column 8: the end of "Stuck in".
|
||||
mv(&mut s, EditUnit::Row, true, false);
|
||||
s.char_input(",");
|
||||
mv(&mut s, EditUnit::Edge, false, false);
|
||||
mv(&mut s, EditUnit::Line, true, true);
|
||||
s.char_input("a cave");
|
||||
assert_eq!(
|
||||
s.eval::<String>("return HelpFrameOpenTicketText:GetText()")
|
||||
.unwrap(),
|
||||
"Stuck in,\na cave"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ pub(super) fn feed_ui_input(
|
||||
}
|
||||
} else if let Some(chord) = chord {
|
||||
// The gate is on the key, not the action: a gated arrow never reaches the box (the
|
||||
// reference's `return 0`), while HOME/END, which also make `Move { unit: Edge }`, do.
|
||||
// reference's `return 0`), while HOME/END, which also make a `Move`, do.
|
||||
let gated_arrow = capture.arrows_fall_through
|
||||
&& matches!(
|
||||
ev.key_code,
|
||||
|
||||
Reference in New Issue
Block a user