refactor(console-ui/W8): split shell.rs into shell/ facade + render/overlays/tests
Break the 1212-line pf-console-ui/src/shell.rs into a facade + shell/ subdir
(shell.rs stays the parent; `mod render;` resolves to shell/render.rs):
- shell/render.rs : the per-frame screen compose/transition path (Shell::render
+ the LayerEnv paint helper)
- shell/overlays.rs : the modal overlays (Shell::draw_overlays + draw_takeover)
- shell/tests.rs : the inline #[cfg(test)] module, extracted verbatim
The Shell struct + its public API + draw_aurora() stay in shell.rs (both children
reach draw_aurora + the private fields as descendants — no bumps). Sole visibility
change: draw_overlays -> pub(in crate::shell) (its caller Shell::render is now a
sibling). Zero re-exports needed (Shell/ConsoleOptions never leave the root). Pure
move; no behavior change.
Verified both platforms: Linux (home-worker-5) clippy -p pf-console-ui
(--all-targets -D warnings) + test; Windows (winbox, ASCII CARGO_HOME) clippy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
//! The console shell's modal overlays (connecting / waking / toast / full-screen takeover).
|
||||
|
||||
use crate::anim::{approach, ease_out_cubic};
|
||||
use crate::glyphs::{hint_bar, Hint, HintKey};
|
||||
use crate::theme::{white, Fonts, PanelStroke, DIM, W, WHITE};
|
||||
use skia_safe::{gradient_shader, Canvas, Color4f, Paint, Point, Rect, TileMode};
|
||||
|
||||
use super::{Shell, BOTTOM_BAND};
|
||||
|
||||
impl Shell {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(in crate::shell) fn draw_overlays(
|
||||
&mut self,
|
||||
canvas: &Canvas,
|
||||
w: f64,
|
||||
h: f64,
|
||||
k: f64,
|
||||
dt: f64,
|
||||
t: f64,
|
||||
fonts: &Fonts,
|
||||
) {
|
||||
// Resolve the connect/wake takeover — the two phases of reaching a host — into one
|
||||
// full-screen shape (spinner, title, one detail line, its own hints). Connecting flows
|
||||
// straight out of a wake (see `sync`) so they share the same backdrop and never blink
|
||||
// between them. Mirrors the Android client's unified `ConnectOverlay`.
|
||||
let takeover: Option<(f64, bool, String, String, Vec<Hint>)> =
|
||||
if let Some(c) = &mut self.connecting {
|
||||
c.appear = approach(c.appear, 1.0, dt, 0.07);
|
||||
if c.canceling {
|
||||
Some((
|
||||
c.appear,
|
||||
true,
|
||||
"Canceling…".to_string(),
|
||||
String::new(),
|
||||
vec![],
|
||||
))
|
||||
} else if c.request_access {
|
||||
Some((
|
||||
c.appear,
|
||||
true,
|
||||
"Waiting for approval…".to_string(),
|
||||
format!(
|
||||
"Approve this device in {}'s console or web UI — no PIN needed.",
|
||||
c.title
|
||||
),
|
||||
vec![Hint::new(HintKey::Back, "Cancel")],
|
||||
))
|
||||
} else {
|
||||
Some((
|
||||
c.appear,
|
||||
true,
|
||||
format!("Connecting to {}…", c.title),
|
||||
"Starting the stream in this window.".to_string(),
|
||||
vec![Hint::new(HintKey::Back, "Cancel")],
|
||||
))
|
||||
}
|
||||
} else if let Some(wk) = &self.wake {
|
||||
// Service-driven, so it appears settled (no fade-in).
|
||||
if wk.timed_out {
|
||||
Some((
|
||||
1.0,
|
||||
false,
|
||||
format!("{} didn't wake", wk.name),
|
||||
"Check its power settings, or wake it manually and try again.".to_string(),
|
||||
vec![
|
||||
Hint::new(HintKey::Confirm, "Try Again"),
|
||||
Hint::new(HintKey::Back, "Cancel"),
|
||||
],
|
||||
))
|
||||
} else {
|
||||
Some((
|
||||
1.0,
|
||||
true,
|
||||
format!("Waking {}…", wk.name),
|
||||
format!("Waiting for it to come online · {} s", wk.seconds),
|
||||
// A wake-only wait (no dial after) offers "Stop Waiting"; a wake-&-connect
|
||||
// is a plain "Cancel".
|
||||
vec![Hint::new(
|
||||
HintKey::Back,
|
||||
if wk.then_connect {
|
||||
"Cancel"
|
||||
} else {
|
||||
"Stop Waiting"
|
||||
},
|
||||
)],
|
||||
))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some((appear, spinner, title, body, hints)) = takeover {
|
||||
self.draw_takeover(
|
||||
canvas, w, h, k, appear, t, fonts, spinner, &title, &body, &hints,
|
||||
);
|
||||
}
|
||||
|
||||
// The toast: a transient pill above the hint bar; slides in, fades out.
|
||||
if self.toast.as_ref().is_some_and(|toast| t - toast.at > 4.0) {
|
||||
self.toast = None;
|
||||
}
|
||||
if let Some(toast) = &self.toast {
|
||||
let age = t - toast.at;
|
||||
{
|
||||
let slide = ease_out_cubic((age / 0.25).min(1.0));
|
||||
let fade = if age > 3.4 {
|
||||
(1.0 - (age - 3.4) / 0.6).max(0.0)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let alpha = (slide * fade) as f32;
|
||||
let size = 13.0 * k;
|
||||
let tw = f64::from(fonts.measure(&toast.text, W::Medium, size));
|
||||
let (pad_x, bh) = (16.0 * k, 34.0 * k);
|
||||
let bw = tw + 2.0 * pad_x;
|
||||
let bx = (w - bw) / 2.0;
|
||||
let by = h - BOTTOM_BAND * k - bh - 8.0 * k + (1.0 - slide) * 12.0 * k;
|
||||
canvas.save_layer_alpha_f(None, alpha);
|
||||
let rect = Rect::from_xywh(bx as f32, by as f32, bw as f32, bh as f32);
|
||||
canvas.draw_rrect(
|
||||
skia_safe::RRect::new_rect_xy(rect, (bh / 2.0) as f32, (bh / 2.0) as f32),
|
||||
&Paint::new(Color4f::new(0.0, 0.0, 0.0, 0.6), None),
|
||||
);
|
||||
crate::theme::panel(
|
||||
canvas,
|
||||
rect,
|
||||
(bh / 2.0 / k) as f32,
|
||||
None,
|
||||
PanelStroke::Plain(0.14),
|
||||
k as f32,
|
||||
);
|
||||
fonts.draw(
|
||||
canvas,
|
||||
&toast.text,
|
||||
bx + pad_x,
|
||||
by + bh / 2.0 + size * 0.36,
|
||||
W::Medium,
|
||||
size,
|
||||
white(0.92),
|
||||
);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
/// A full-screen connect/wake takeover: a fresh aurora over everything (so the carousel and
|
||||
/// chrome fall away), a centered spinner (or none, when a wake has timed out), a title, one
|
||||
/// detail line, and its own bottom hint row. `appear` fades the whole thing in over the home;
|
||||
/// a wake that hands off to a connect passes 1.0 so the two never blink between them. The
|
||||
/// console counterpart of the Android/Apple `ConnectOverlay` — one full-screen shape, not a
|
||||
/// centered modal card.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn draw_takeover(
|
||||
&self,
|
||||
canvas: &Canvas,
|
||||
w: f64,
|
||||
h: f64,
|
||||
k: f64,
|
||||
appear: f64,
|
||||
t: f64,
|
||||
fonts: &Fonts,
|
||||
spinner: bool,
|
||||
title: &str,
|
||||
body: &str,
|
||||
hints: &[Hint],
|
||||
) {
|
||||
let cx = w / 2.0;
|
||||
canvas.save_layer_alpha_f(None, appear as f32);
|
||||
// Opaque aurora — the same living backdrop the home wears, so the takeover reads as the
|
||||
// console taking over rather than a card popping up.
|
||||
self.draw_aurora(canvas, w, h, t);
|
||||
// A soft pool of shade under the centre seats the white text against a bright aurora.
|
||||
let mut vignette = Paint::default();
|
||||
vignette.set_shader(gradient_shader::radial(
|
||||
Point::new(cx as f32, (h / 2.0) as f32),
|
||||
(w.max(h) * 0.42) as f32,
|
||||
gradient_shader::GradientShaderColors::Colors(&[
|
||||
Color4f::new(0.0, 0.0, 0.0, 0.5).to_color(),
|
||||
Color4f::new(0.0, 0.0, 0.0, 0.0).to_color(),
|
||||
]),
|
||||
None,
|
||||
TileMode::Clamp,
|
||||
None,
|
||||
None,
|
||||
));
|
||||
canvas.draw_rect(Rect::from_wh(w as f32, h as f32), &vignette);
|
||||
|
||||
// Centre the spinner + title + detail as a group around the middle of the screen.
|
||||
let title_y = h / 2.0 + if spinner { 14.0 * k } else { 0.0 };
|
||||
if spinner {
|
||||
crate::theme::spinner(canvas, cx, title_y - 52.0 * k, 22.0 * k, t);
|
||||
}
|
||||
fonts.centered(
|
||||
canvas,
|
||||
title,
|
||||
W::SemiBold,
|
||||
23.0 * k,
|
||||
WHITE,
|
||||
cx,
|
||||
title_y,
|
||||
w * 0.82,
|
||||
);
|
||||
if !body.is_empty() {
|
||||
fonts.centered(
|
||||
canvas,
|
||||
body,
|
||||
W::Regular,
|
||||
14.0 * k,
|
||||
DIM,
|
||||
cx,
|
||||
title_y + 32.0 * k,
|
||||
w * 0.66,
|
||||
);
|
||||
}
|
||||
if !hints.is_empty() {
|
||||
// Centered near the bottom, where every console screen's legend sits.
|
||||
let probe = hint_bar(canvas, fonts, hints, self.glyphs, -10_000.0, -10_000.0, k);
|
||||
hint_bar(
|
||||
canvas,
|
||||
fonts,
|
||||
hints,
|
||||
self.glyphs,
|
||||
cx - probe.0 / 2.0,
|
||||
h - 34.0 * k,
|
||||
k,
|
||||
);
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user