Preserve input focus across passive overlays
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
RuDS is a native Rust blogging desktop application and the successor to bDS2. It manages local projects from authoring through preview, static-site generation, integrity checks, and publishing while preserving the existing bDS filesystem and SQLite formats.
|
RuDS is a native Rust blogging desktop application and the successor to bDS2. It manages local projects from authoring through preview, static-site generation, integrity checks, and publishing while preserving the existing bDS filesystem and SQLite formats.
|
||||||
|
|
||||||
The desktop is a single-window application: closing its window persists UI state and exits RuDS.
|
The desktop is a single-window application: closing its window persists UI state and exits RuDS. Background notifications remain unobtrusive and preserve keyboard focus while editing.
|
||||||
|
|
||||||
The project is under active development. Core blogging workflows are broadly available; remaining core work and optional extensions are tracked separately.
|
The project is under active development. Core blogging workflows are broadly available; remaining core work and optional extensions are tracked separately.
|
||||||
|
|
||||||
|
|||||||
@@ -387,16 +387,17 @@ pub fn view<'a>(
|
|||||||
overlays.push(modal::view(modal_state, locale, data_dir));
|
overlays.push(modal::view(modal_state, locale, data_dir));
|
||||||
}
|
}
|
||||||
|
|
||||||
if overlays.is_empty() {
|
workspace_with_overlays(base_layout, overlays)
|
||||||
base_layout
|
}
|
||||||
} else {
|
|
||||||
let mut layers = vec![base_layout];
|
fn workspace_with_overlays<'a>(
|
||||||
layers.extend(overlays);
|
base_layout: Element<'a, Message>,
|
||||||
stack(layers)
|
overlays: Vec<Element<'a, Message>>,
|
||||||
.width(Length::Fill)
|
) -> Element<'a, Message> {
|
||||||
.height(Length::Fill)
|
stack(std::iter::once(base_layout).chain(overlays))
|
||||||
.into()
|
.width(Length::Fill)
|
||||||
}
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Route the content area based on the active tab type.
|
/// Route the content area based on the active tab type.
|
||||||
@@ -711,6 +712,11 @@ fn loading_view<'a>(locale: UiLocale) -> Element<'a, Message> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::state::tabs::{Tab, TabType};
|
use crate::state::tabs::{Tab, TabType};
|
||||||
|
use iced::advanced::widget::Tree;
|
||||||
|
use iced::widget::text_input;
|
||||||
|
|
||||||
|
type TextInputState =
|
||||||
|
text_input::State<<iced::Renderer as iced::advanced::text::Renderer>::Paragraph>;
|
||||||
|
|
||||||
fn tab(id: &str, tab_type: TabType, title: &str) -> Tab {
|
fn tab(id: &str, tab_type: TabType, title: &str) -> Tab {
|
||||||
Tab {
|
Tab {
|
||||||
@@ -722,6 +728,38 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overlays_preserve_the_focused_workspace_input() {
|
||||||
|
let base = || -> Element<'static, Message> {
|
||||||
|
text_input("Commit message", "draft")
|
||||||
|
.on_input(|_| Message::Noop)
|
||||||
|
.into()
|
||||||
|
};
|
||||||
|
let mut tree = Tree::new(workspace_with_overlays(base(), Vec::new()).as_widget());
|
||||||
|
tree.children[0]
|
||||||
|
.state
|
||||||
|
.downcast_mut::<TextInputState>()
|
||||||
|
.focus();
|
||||||
|
|
||||||
|
let with_toast = workspace_with_overlays(base(), vec![text("Done").into()]);
|
||||||
|
tree.diff(with_toast.as_widget());
|
||||||
|
assert!(
|
||||||
|
tree.children[0]
|
||||||
|
.state
|
||||||
|
.downcast_ref::<TextInputState>()
|
||||||
|
.is_focused()
|
||||||
|
);
|
||||||
|
|
||||||
|
let without_toast = workspace_with_overlays(base(), Vec::new());
|
||||||
|
tree.diff(without_toast.as_widget());
|
||||||
|
assert!(
|
||||||
|
tree.children[0]
|
||||||
|
.state
|
||||||
|
.downcast_ref::<TextInputState>()
|
||||||
|
.is_focused()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn style_tab_routes_to_real_editor_only_with_project_state() {
|
fn style_tab_routes_to_real_editor_only_with_project_state() {
|
||||||
let empty_posts = HashMap::new();
|
let empty_posts = HashMap::new();
|
||||||
|
|||||||
@@ -210,6 +210,11 @@ invariant ActivityActiveHighlight {
|
|||||||
btn.active = (active_view = btn.id and sidebar_visible)
|
btn.active = (active_view = btn.id and sidebar_visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invariant PassiveOverlayFocusStability {
|
||||||
|
-- Adding or removing passive overlays such as toasts and background-task
|
||||||
|
-- notifications never changes the keyboard-focused control.
|
||||||
|
}
|
||||||
|
|
||||||
-- ─── Git badge ────────────────────────────────────────────────
|
-- ─── Git badge ────────────────────────────────────────────────
|
||||||
|
|
||||||
-- Only the git activity button carries a badge.
|
-- Only the git activity button carries a badge.
|
||||||
|
|||||||
Reference in New Issue
Block a user