Let Back close the presets menu instead of the application
The presets menu at the foot of the tool rail is a PopupWindow, and showing a popup takes focus off the develop view until it closes. The menu held nothing focusable, so Android's Back gesture, pressed to dismiss it, found no focus item, went unanswered, and the platform closed the application. Slint closes a popup on Escape by itself but not on Back. The menu now holds a key scope, as the film list does, that closes it on Back or Escape. The next Back leaves develop for the grid.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,69 @@
|
||||
// TRACES: FR-UI-5
|
||||
//! Back closes the presets menu, and the next Back returns to the grid.
|
||||
//!
|
||||
//! The menu at the foot of the tool rail is a `PopupWindow`, and showing a
|
||||
//! popup takes focus off the develop view until it closes. The menu had
|
||||
//! nothing focusable of its own, so a Back pressed while it was up had no
|
||||
//! focus item to bubble from, went unanswered, and on Android an unanswered
|
||||
//! Back is the platform's: it closed the application from develop. Dismissing
|
||||
//! a menu with the back gesture is the ordinary thing to do, which is why this
|
||||
//! was seen often.
|
||||
//!
|
||||
//! Runs on Slint's testing backend, as `film_list_reaches_every_stock` does.
|
||||
#![cfg(debug_assertions)]
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use dr_ui::{AppWindow, Develop};
|
||||
use i_slint_backend_testing::{init_no_event_loop, ElementHandle};
|
||||
use slint::platform::{Key, PointerEventButton, WindowEvent};
|
||||
use slint::{ComponentHandle, SharedString};
|
||||
|
||||
const MANAGE: &str = "Save or manage…";
|
||||
|
||||
fn key(app: &AppWindow, k: Key) {
|
||||
let text: SharedString = k.into();
|
||||
app.window()
|
||||
.dispatch_event(WindowEvent::KeyPressed { text: text.clone() });
|
||||
app.window()
|
||||
.dispatch_event(WindowEvent::KeyReleased { text });
|
||||
}
|
||||
|
||||
fn menu_open(app: &AppWindow) -> bool {
|
||||
ElementHandle::find_by_accessible_label(app, MANAGE)
|
||||
.next()
|
||||
.is_some()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn back_closes_the_presets_menu_then_leaves_develop() {
|
||||
init_no_event_loop();
|
||||
let app = AppWindow::new().expect("the window builds on the testing backend");
|
||||
app.window().set_size(slint::LogicalSize::new(1600., 1200.));
|
||||
app.global::<Develop>().set_enabled(true);
|
||||
// The rail offers presets only with a photograph to apply them to.
|
||||
app.set_total(3);
|
||||
let asked = Rc::new(Cell::new(0));
|
||||
app.on_back_requested({
|
||||
let asked = asked.clone();
|
||||
move || {
|
||||
asked.set(asked.get() + 1);
|
||||
true
|
||||
}
|
||||
});
|
||||
app.show().unwrap();
|
||||
|
||||
ElementHandle::find_by_accessible_label(&app, "Presets")
|
||||
.next()
|
||||
.expect("the tool rail's presets entry is on screen")
|
||||
.mock_single_click(PointerEventButton::Left);
|
||||
assert!(menu_open(&app), "clicking Presets opens the menu");
|
||||
|
||||
key(&app, Key::Back);
|
||||
assert!(!menu_open(&app), "Back closes the menu");
|
||||
assert_eq!(asked.get(), 0, "closing the menu is the whole step");
|
||||
|
||||
key(&app, Key::Back);
|
||||
assert_eq!(asked.get(), 1, "the next Back reaches the shell");
|
||||
}
|
||||
@@ -55,6 +55,7 @@ import { Icon } from "icons.slint";
|
||||
import { ViewMode } from "session.slint";
|
||||
import { PresetRow, PresetOrigin, PresetFolderRow } from "presets.slint";
|
||||
import { Label, ScrollBar } from "widgets.slint";
|
||||
import { Keys } from "keys.slint";
|
||||
|
||||
// One tool. A struct rather than four parallel arrays so a row cannot be
|
||||
// half-added — the compiler will not let a new entry omit its icon.
|
||||
@@ -522,6 +523,30 @@ export component ToolRail inherits Rectangle {
|
||||
root.height - Theme.gap);
|
||||
close-policy: PopupClosePolicy.close-on-click-outside;
|
||||
|
||||
// Somewhere for Back to start from while the menu is up.
|
||||
//
|
||||
// A popup is its own focus tree: showing it takes focus off the
|
||||
// develop view, and Slint hands it back only when the popup
|
||||
// closes. With nothing focusable in here, Android's Back gesture
|
||||
// — the natural way to dismiss a menu — found no focus item,
|
||||
// went unanswered, and the platform closed the application.
|
||||
// Slint closes a popup on Escape by itself but knows nothing of
|
||||
// Back, so both are answered here, as the film list does.
|
||||
FocusScope {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
init => { self.focus(); }
|
||||
|
||||
// KEYMAP: Develop
|
||||
key-pressed(event) => {
|
||||
if (Keys.chord(event) == "Escape" || Keys.chord(event) == "Back") {
|
||||
preset-menu.close();
|
||||
return accept;
|
||||
}
|
||||
return reject;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
background: Theme.surface;
|
||||
border-radius: Theme.radius;
|
||||
|
||||
Reference in New Issue
Block a user