Mouse input improvements

This commit is contained in:
2026-02-28 17:56:09 +00:00
parent 7c1aad84af
commit 8b8703b9b9
4 changed files with 381 additions and 41 deletions

View File

@@ -256,6 +256,25 @@ impl Widget for KeyboardDiagram<'_> {
}
impl KeyboardDiagram<'_> {
pub fn key_at_position(
area: Rect,
model: &KeyboardModel,
compact: bool,
x: u16,
y: u16,
) -> Option<char> {
let inner = Block::bordered().inner(area);
if compact {
return key_at_compact_position(inner, model, x, y);
}
if inner.height >= 4 && inner.width >= 75 {
key_at_full_position(inner, model, x, y)
} else {
key_at_full_fallback_position(inner, model, x, y)
}
}
fn render_compact(&self, inner: Rect, buf: &mut Buffer) {
let colors = &self.theme.colors;
let letter_rows = self.model.letter_rows();
@@ -597,3 +616,230 @@ impl KeyboardDiagram<'_> {
}
}
}
fn rect_contains(area: Rect, x: u16, y: u16) -> bool {
x >= area.x && x < area.x + area.width && y >= area.y && y < area.y + area.height
}
fn key_at_compact_position(inner: Rect, model: &KeyboardModel, x: u16, y: u16) -> Option<char> {
let letter_rows = model.letter_rows();
let key_width: u16 = 3;
let min_width: u16 = 21;
if inner.height < 3 || inner.width < min_width {
return None;
}
let offsets: &[u16] = &[3, 4, 6];
let keyboard_width = letter_rows
.iter()
.enumerate()
.map(|(row_idx, row)| {
let offset = offsets.get(row_idx).copied().unwrap_or(0);
let row_end = offset + row.len() as u16 * key_width;
match row_idx {
0 => row_end + 3,
1 => row_end + 3,
2 => row_end + 3,
_ => row_end,
}
})
.max()
.unwrap_or(0);
let start_x = inner.x + inner.width.saturating_sub(keyboard_width) / 2;
for (row_idx, row) in letter_rows.iter().enumerate() {
let row_y = inner.y + row_idx as u16;
if y != row_y {
continue;
}
match row_idx {
0 => {
let tab_rect = Rect::new(start_x, row_y, 3, 1);
if rect_contains(tab_rect, x, y) {
return Some(TAB);
}
}
2 => {
let shft_rect = Rect::new(start_x, row_y, 3, 1);
if rect_contains(shft_rect, x, y) {
return None;
}
}
_ => {}
}
let offset = offsets.get(row_idx).copied().unwrap_or(0);
for (col_idx, key) in row.iter().enumerate() {
let key_x = start_x + offset + col_idx as u16 * key_width;
let key_rect = Rect::new(key_x, row_y, 3, 1);
if rect_contains(key_rect, x, y) {
return Some(key.base);
}
}
let row_end_x = start_x + offset + row.len() as u16 * key_width;
match row_idx {
1 => {
let enter_rect = Rect::new(row_end_x, row_y, 3, 1);
if rect_contains(enter_rect, x, y) {
return Some(ENTER);
}
}
2 => {
let shft_rect = Rect::new(row_end_x, row_y, 3, 1);
if rect_contains(shft_rect, x, y) {
return None;
}
}
_ => {}
}
}
if inner.height >= 3 {
let y0 = inner.y;
let row_end_x = start_x + offsets[0] + letter_rows[0].len() as u16 * key_width;
let back_rect = Rect::new(row_end_x, y0, 3, 1);
if rect_contains(back_rect, x, y) {
return Some(BACKSPACE);
}
}
None
}
fn key_at_full_position(inner: Rect, model: &KeyboardModel, x: u16, y: u16) -> Option<char> {
let key_width: u16 = 5;
let offsets: &[u16] = &[0, 5, 5, 6];
let keyboard_width = model
.rows
.iter()
.enumerate()
.map(|(row_idx, row)| {
let offset = offsets.get(row_idx).copied().unwrap_or(0);
let row_end = offset + row.len() as u16 * key_width;
match row_idx {
0 => row_end + 6,
2 => row_end + 7,
3 => row_end + 6,
_ => row_end,
}
})
.max()
.unwrap_or(0);
let start_x = inner.x + inner.width.saturating_sub(keyboard_width) / 2;
for (row_idx, row) in model.rows.iter().enumerate() {
let row_y = inner.y + row_idx as u16;
if y != row_y {
continue;
}
let offset = offsets.get(row_idx).copied().unwrap_or(0);
match row_idx {
1 => {
let tab_rect = Rect::new(start_x, row_y, 5, 1);
if rect_contains(tab_rect, x, y) {
return Some(TAB);
}
}
2 => {
let cap_rect = Rect::new(start_x, row_y, 5, 1);
if rect_contains(cap_rect, x, y) {
return None;
}
}
3 => {
let shft_rect = Rect::new(start_x, row_y, 6, 1);
if rect_contains(shft_rect, x, y) {
return None;
}
}
_ => {}
}
for (col_idx, key) in row.iter().enumerate() {
let key_x = start_x + offset + col_idx as u16 * key_width;
let key_rect = Rect::new(key_x, row_y, key_width, 1);
if rect_contains(key_rect, x, y) {
return Some(key.base);
}
}
let after_x = start_x + offset + row.len() as u16 * key_width;
match row_idx {
0 => {
let rect = Rect::new(after_x, row_y, 6, 1);
if rect_contains(rect, x, y) {
return Some(BACKSPACE);
}
}
2 => {
let rect = Rect::new(after_x, row_y, 7, 1);
if rect_contains(rect, x, y) {
return Some(ENTER);
}
}
3 => {
let rect = Rect::new(after_x, row_y, 6, 1);
if rect_contains(rect, x, y) {
return None;
}
}
_ => {}
}
}
let space_y = inner.y + 4;
if y == space_y {
let space_name = display::key_display_name(SPACE);
let space_label = format!("[ {space_name} ]");
let space_width = space_label.len() as u16;
let space_x = start_x + (keyboard_width.saturating_sub(space_width)) / 2;
let space_rect = Rect::new(space_x, space_y, space_width, 1);
if rect_contains(space_rect, x, y) {
return Some(SPACE);
}
}
None
}
fn key_at_full_fallback_position(
inner: Rect,
model: &KeyboardModel,
x: u16,
y: u16,
) -> Option<char> {
let letter_rows = model.letter_rows();
let key_width: u16 = 5;
let offsets: &[u16] = &[1, 3, 5];
let keyboard_width = letter_rows
.iter()
.enumerate()
.map(|(row_idx, row)| {
let offset = offsets.get(row_idx).copied().unwrap_or(0);
offset + row.len() as u16 * key_width
})
.max()
.unwrap_or(0);
let start_x = inner.x + inner.width.saturating_sub(keyboard_width) / 2;
if inner.height < 3 || inner.width < 30 {
return None;
}
for (row_idx, row) in letter_rows.iter().enumerate() {
let row_y = inner.y + row_idx as u16;
if y != row_y {
continue;
}
let offset = offsets.get(row_idx).copied().unwrap_or(0);
for (col_idx, key) in row.iter().enumerate() {
let key_x = start_x + offset + col_idx as u16 * key_width;
let key_rect = Rect::new(key_x, row_y, key_width, 1);
if rect_contains(key_rect, x, y) {
return Some(key.base);
}
}
}
None
}