mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-03 16:41:26 +01:00
feat(app): keyboard navigation across the library and episode list
Tiles and episode stills light up under focus as they do under the pointer and scroll into view when the arrow keys or Tab reach them; Enter opens or plays. Global keys now check for a focused text field rather than any focused widget, so / and Esc still work over a tile.
This commit is contained in:
1 parent
79c3f6885d
commit
d3325843a9
4 files changed
+33
-7
No files matched your search
@@ -1901,7 +1901,9 @@ fn changed_art(
|
|||||||
fn global_shortcuts(ctx: &egui::Context, page: &Page, actions: &mut Vec<Action>) {
|
fn global_shortcuts(ctx: &egui::Context, page: &Page, actions: &mut Vec<Action>) {
|
||||||
use egui::{Key, Modifiers};
|
use egui::{Key, Modifiers};
|
||||||
|
|
||||||
let typing = ctx.egui_wants_keyboard_input();
|
// A text field, not any focused widget: a tile reached with Tab or the
|
||||||
|
// arrow keys has focus too, and "/" over it should still search.
|
||||||
|
let typing = ctx.text_edit_focused();
|
||||||
let mut tab = None;
|
let mut tab = None;
|
||||||
ctx.input_mut(|input| {
|
ctx.input_mut(|input| {
|
||||||
if input.consume_key(Modifiers::COMMAND, Key::F)
|
if input.consume_key(Modifiers::COMMAND, Key::F)
|
||||||
|
|||||||
@@ -514,6 +514,11 @@ pub fn card(ui: &mut egui::Ui, card: Card<'_>) -> egui::Response {
|
|||||||
|
|
||||||
let (rect, response) =
|
let (rect, response) =
|
||||||
ui.allocate_exact_size(Vec2::new(width, image_height + text_height), Sense::click());
|
ui.allocate_exact_size(Vec2::new(width, image_height + text_height), Sense::click());
|
||||||
|
// Reached with the arrow keys or Tab: bring it into view, before the
|
||||||
|
// early return below would skip a tile that is just off screen.
|
||||||
|
if response.gained_focus() {
|
||||||
|
response.scroll_to_me(None);
|
||||||
|
}
|
||||||
if !ui.is_rect_visible(rect) {
|
if !ui.is_rect_visible(rect) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -523,9 +528,13 @@ pub fn card(ui: &mut egui::Ui, card: Card<'_>) -> egui::Response {
|
|||||||
// ride it, so they arrive together rather than each snapping on its own.
|
// ride it, so they arrive together rather than each snapping on its own.
|
||||||
// egui drives this from wall-clock time, so it is the same speed whatever
|
// egui drives this from wall-clock time, so it is the same speed whatever
|
||||||
// the frame rate — and it needs no state kept here.
|
// the frame rate — and it needs no state kept here.
|
||||||
let hover =
|
// Focus lights a tile the way the pointer does, so the keyboard can be
|
||||||
ui.ctx()
|
// followed across the grid.
|
||||||
.animate_bool_with_time(response.id.with("hover"), response.hovered(), 0.12);
|
let hover = ui.ctx().animate_bool_with_time(
|
||||||
|
response.id.with("hover"),
|
||||||
|
response.hovered() || response.has_focus(),
|
||||||
|
0.12,
|
||||||
|
);
|
||||||
// The picture arrives whenever its download finishes, several frames after
|
// The picture arrives whenever its download finishes, several frames after
|
||||||
// the tile first drew. Fading it in over the placeholder turns a wall of
|
// the tile first drew. Fading it in over the placeholder turns a wall of
|
||||||
// letters popping into stills into one settle.
|
// letters popping into stills into one settle.
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const GROUPS: &[(&str, &[(&str, &str)])] = &[
|
|||||||
("Ctrl 1 – 4", "Library, Shares, Downloads, Settings"),
|
("Ctrl 1 – 4", "Library, Shares, Downloads, Settings"),
|
||||||
("Ctrl ,", "Settings"),
|
("Ctrl ,", "Settings"),
|
||||||
("Ctrl V", "Paste a share link to add it"),
|
("Ctrl V", "Paste a share link to add it"),
|
||||||
|
("Tab or arrows", "Move between titles and episodes"),
|
||||||
|
("Enter", "Open or play what is highlighted"),
|
||||||
("Alt ← or mouse back", "Back"),
|
("Alt ← or mouse back", "Back"),
|
||||||
("?", "This list"),
|
("?", "This list"),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -740,9 +740,14 @@ fn still(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hover = ui
|
if response.gained_focus() {
|
||||||
.ctx()
|
response.scroll_to_me(None);
|
||||||
.animate_bool_with_time(response.id, response.hovered(), 0.12);
|
}
|
||||||
|
let hover = ui.ctx().animate_bool_with_time(
|
||||||
|
response.id,
|
||||||
|
response.hovered() || response.has_focus(),
|
||||||
|
0.12,
|
||||||
|
);
|
||||||
if hover > 0.0 {
|
if hover > 0.0 {
|
||||||
painter.rect_filled(
|
painter.rect_filled(
|
||||||
rect,
|
rect,
|
||||||
@@ -750,6 +755,14 @@ fn still(
|
|||||||
egui::Color32::from_black_alpha((90.0 * hover) as u8),
|
egui::Color32::from_black_alpha((90.0 * hover) as u8),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if response.has_focus() {
|
||||||
|
painter.rect_stroke(
|
||||||
|
rect,
|
||||||
|
radius,
|
||||||
|
egui::Stroke::new(2.0, theme::accent()),
|
||||||
|
egui::StrokeKind::Inside,
|
||||||
|
);
|
||||||
|
}
|
||||||
// A play mark, always faintly there and full under the pointer.
|
// A play mark, always faintly there and full under the pointer.
|
||||||
let centre = rect.center();
|
let centre = rect.center();
|
||||||
painter.circle_filled(
|
painter.circle_filled(
|
||||||
|
|||||||
Reference in new issue
Block a user