chore(deps): move pf-console-ui from skia-safe 0.87 to 0.99

Twelve skia-safe releases (0.88 … 0.99), carrying Skia milestones 140 through
150, every one of them breaking under 0.x semver. Only three of those changes
actually reach this crate — the Vulkan surface/backend-texture path, the
textlayout/paragraph typography and RuntimeEffect all came through untouched:

* m143 (skia-safe 0.91) DELETED SkPath's mutating API. `Path::new()` followed by
  `move_to`/`line_to`/`arc_to`/`close` no longer compiles at all — geometry is
  built through `PathBuilder` now and frozen with `snapshot()`/`detach()`. That
  is the entire error list: 34 E0599s over eight call sites (the hint-bar
  triangles and the PlayStation triangle in `glyphs.rs`, the chevron / space /
  backspace / check icons in `widgets.rs`, the padlock shackle in
  `screens/home.rs`). Each becomes a `PathBuilder` detached at the draw call, so
  the path is still built and thrown away once per draw exactly as before.

* 0.93 deprecated the `gradient_shader` module in favour of `gradient`. Only a
  warning, but the Format/clippy gate runs `-D warnings`, so it is a hard break
  for us. The three gradients — the panel stroke in `theme.rs`, the
  connect-overlay vignette in `shell/overlays.rs`, the host monogram in
  `screens/home.rs` — now build a `gradient::Gradient` from
  `gradient::Colors::new_evenly_spaced` plus `Interpolation::default()`. That
  default (unpremul interpolation, destination colour space, shorter hue) is
  what the old `flags: None` argument mapped to, so the pixels do not move. The
  new API takes `Color4f` directly, which drops the `.to_color()` 8-bit
  round-trip the old signature forced.

* 0.98 deprecated `vk::BackendContext::new` in favour of a builder (upstream
  #1292). `skia_overlay.rs` now calls
  `BackendContext::new_builder(...)` + `build()`, passing `max_api_version:
  None` so Skia keeps deriving its cap from `vkEnumerateInstanceVersion()` —
  bit-for-bit what `new()` passed. `build()` is the unsafe half, so the SAFETY
  proof moved down onto it.

`ash` is untouched and stays on the workspace's exact `=0.38.0+1.3.281` pin:
skia-safe lists ash only as a DEV dependency, so the bump cannot reach it.

The prebuilt-binary assumption still holds — verified from the build log, not
from the release page: skia-bindings printed `DOWNLOAD AND INSTALL SUCCEEDED`
for
`skia-binaries-a25a0fdb7d90429aa2d1-x86_64-unknown-linux-gnu-jpegd-jpege-pdf-textlayout-vulkan`,
so no CI leg compiles Skia from source. The asset name DID change: `jpeg` joined
skia-safe's default feature set between 0.87 and 0.99, so `jpegd-jpege` is now
in the name. We take defaults, so the JPEG codecs came along — which is a fix in
disguise, since `screens/library.rs` hands host poster art straight to
`Image::from_encoded`, and JPEG posters used to fall out as "undecodable". The
Cargo.toml comment now records the verified asset names and the silent-source-
build trap for the next bump.

Verified on 192.168.1.21, x86_64-unknown-linux-gnu, toolchain 1.96.0:
  cargo build  -p pf-console-ui                              exit 0
  cargo clippy -p pf-console-ui --all-targets -- -D warnings  exit 0
  cargo test   -p pf-console-ui                              82 passed, 1 ignored
  cargo fmt --all --check                                    exit 0
This commit is contained in:
2026-08-13 13:40:50 +02:00
parent c95db8eebc
commit 1fb081a1f0
8 changed files with 86 additions and 126 deletions
Generated
+8 -63
View File
@@ -3454,7 +3454,7 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
"toml_edit 0.25.13+spec-1.1.0",
"toml_edit",
]
[[package]]
@@ -4524,15 +4524,6 @@ dependencies = [
"syn 3.0.3",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "1.1.1"
@@ -4636,30 +4627,28 @@ checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "skia-bindings"
version = "0.87.0"
version = "0.99.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704242769235d2ffe66a2a0a3002661262fc4af08d32807c362d7b0160ee703c"
checksum = "3e2d1c3ebd697c0cbded0145e9204a38fa6b268446051b7196d0a096414ea7f3"
dependencies = [
"bindgen",
"cc",
"flate2",
"heck",
"lazy_static",
"pkg-config",
"regex",
"serde_json",
"tar",
"toml 0.8.23",
"toml 1.1.4+spec-1.1.0",
]
[[package]]
name = "skia-safe"
version = "0.87.0"
version = "0.99.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f7d94f3e7537c71ad4cf132eb26e3be8c8a886ed3649c4525c089041fc312b2"
checksum = "9f512ac418a64194842dd05566320805dad1c957c521039db2486fd6368865bc"
dependencies = [
"bitflags 2.13.1",
"lazy_static",
"skia-bindings",
]
@@ -5039,18 +5028,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"toml_edit 0.22.27",
]
[[package]]
name = "toml"
version = "0.9.12+spec-1.1.0"
@@ -5059,7 +5036,7 @@ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned 1.1.1",
"serde_spanned",
"toml_datetime 0.7.5+spec-1.1.0",
"toml_parser",
"toml_writer",
@@ -5074,22 +5051,13 @@ checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned 1.1.1",
"serde_spanned",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"toml_writer",
"winnow 1.0.4",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
@@ -5108,20 +5076,6 @@ dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"toml_write",
"winnow 0.7.15",
]
[[package]]
name = "toml_edit"
version = "0.25.13+spec-1.1.0"
@@ -5143,12 +5097,6 @@ dependencies = [
"winnow 1.0.4",
]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "toml_writer"
version = "1.1.2+spec-1.1.0"
@@ -6169,9 +6117,6 @@ name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
dependencies = [
"memchr",
]
[[package]]
name = "winnow"
+12 -1
View File
@@ -22,7 +22,18 @@ pf-client-core = { path = "../pf-client-core", default-features = false }
# the typography the console library needs (~15 MB stripped, prebuilt binaries exist for
# this feature set on x86_64-unknown-linux-gnu AND x86_64-pc-windows-msvc — a source
# build is never triggered on either).
skia-safe = { version = "0.87", features = ["vulkan", "textlayout"] }
#
# The prebuilt-binary claim is the whole reason this dep is affordable, so re-verify it on
# EVERY bump: the build log must say `DOWNLOAD AND INSTALL SUCCEEDED`. skia-bindings does not
# fail when no matching asset exists — it silently falls back to a gn/ninja build of Skia from
# source, which turns a 2-minute CI leg into a multi-hour one. Verified at 0.99.0, both targets:
# skia-binaries-a25a0fdb7d90429aa2d1-<target>-jpegd-jpege-pdf-textlayout-vulkan.tar.gz
# ⚠ The asset name CHANGED across this bump — at 0.87 it was `<target>-pdf-textlayout-vulkan`,
# because `jpeg` was not yet in skia-safe's DEFAULT feature set (0.87: binary-cache, embed-icudtl,
# pdf; 0.99: + jpeg). We take defaults, so the JPEG codecs came along with the bump. That is a
# feature here rather than bloat: `screens/library.rs` feeds host poster art straight to
# `Image::from_encoded`, which silently returned `None` for JPEG posters before.
skia-safe = { version = "0.99", features = ["vulkan", "textlayout"] }
ash = { version = "0.38", features = ["loaded"] }
anyhow = "1"
+9 -9
View File
@@ -7,7 +7,7 @@
use crate::theme::{fg, Fonts, W};
use punktfunk_core::config::GamepadPref;
use skia_safe::{Canvas, Paint, Path, Point, RRect, Rect};
use skia_safe::{Canvas, Paint, PathBuilder, Point, RRect, Rect};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum GlyphStyle {
@@ -294,12 +294,12 @@ fn draw_glyph(
let r = BADGE_D * k / 2.0;
let (cx, cyf) = ((x + r) as f32, cy as f32);
let (tw, th) = ((5.5 * k) as f32, (4.5 * k) as f32);
let mut up = Path::new();
let mut up = PathBuilder::new();
up.move_to((cx, cyf - th));
up.line_to((cx - tw, cyf + th));
up.line_to((cx + tw, cyf + th));
up.close();
canvas.draw_path(&up, &Paint::new(fg(0.85), None));
canvas.draw_path(&up.detach(), &Paint::new(fg(0.85), None));
}
Resolved::Adjust => {
// ◀ ▶ — two small solid triangles.
@@ -308,18 +308,18 @@ fn draw_glyph(
let (tw, th) = ((4.5 * k) as f32, (5.5 * k) as f32);
let gap = (2.6 * k) as f32;
let paint = Paint::new(fg(0.85), None);
let mut left = Path::new();
let mut left = PathBuilder::new();
left.move_to((cx - gap, cyf - th));
left.line_to((cx - gap - tw, cyf));
left.line_to((cx - gap, cyf + th));
left.close();
canvas.draw_path(&left, &paint);
let mut right = Path::new();
canvas.draw_path(&left.detach(), &paint);
let mut right = PathBuilder::new();
right.move_to((cx + gap, cyf - th));
right.line_to((cx + gap + tw, cyf));
right.line_to((cx + gap, cyf + th));
right.close();
canvas.draw_path(&right, &paint);
canvas.draw_path(&right.detach(), &paint);
}
Resolved::Key(text) => {
let w = keycap_w(fonts, text, k);
@@ -377,12 +377,12 @@ fn draw_ps_shape(canvas: &Canvas, face: Face, center: Point, r: f32, stroke: f32
}
Face::Y => {
// △
let mut tri = Path::new();
let mut tri = PathBuilder::new();
tri.move_to((cx, cy - r * 1.2));
tri.line_to((cx + r * 1.15, cy + r * 0.85));
tri.line_to((cx - r * 1.15, cy + r * 0.85));
tri.close();
canvas.draw_path(&tri, &p);
canvas.draw_path(&tri.detach(), &p);
}
}
}
+13 -11
View File
@@ -13,7 +13,7 @@ use crate::pointer::{Pointer, PointerKind};
use crate::screens::{ConnectIntent, Ctx, Outbox, Screen};
use crate::theme::{accent, fg, Fonts, PanelStroke, ONLINE_GREEN, W};
use pf_client_core::gamepad::{MenuDir, MenuEvent, MenuPulse};
use skia_safe::{Canvas, Color4f, MaskFilter, Paint, Path, Point, RRect, Rect};
use skia_safe::{Canvas, Color4f, MaskFilter, Paint, PathBuilder, Point, RRect, Rect};
const TILE_W: f64 = 340.0;
const TILE_H: f64 = 224.0;
@@ -526,18 +526,20 @@ fn draw_monogram(canvas: &Canvas, fonts: &Fonts, name: &str, filled: bool, x: f6
let rr = RRect::new_rect_xy(badge, (15.0 * k) as f32, (15.0 * k) as f32);
if filled {
let mut p = Paint::default();
p.set_shader(skia_safe::gradient_shader::linear(
let colors = [accent(1.0), accent(0.68)];
p.set_shader(skia_safe::gradient::shaders::linear_gradient(
(
Point::new(badge.left, badge.top),
Point::new(badge.left, badge.bottom),
),
skia_safe::gradient_shader::GradientShaderColors::Colors(&[
accent(1.0).to_color(),
accent(0.68).to_color(),
]),
None,
skia_safe::TileMode::Clamp,
None,
&skia_safe::gradient::Gradient::new(
skia_safe::gradient::Colors::new_evenly_spaced(
&colors,
skia_safe::TileMode::Clamp,
None,
),
skia_safe::gradient::Interpolation::default(),
),
None,
));
canvas.draw_rrect(rr, &p);
@@ -586,7 +588,7 @@ fn draw_lock(canvas: &Canvas, x: f64, y: f64, k: f64) {
p.set_style(skia_safe::PaintStyle::Stroke);
p.set_stroke_width((1.6 * k) as f32);
p.set_anti_alias(true);
let mut shackle = Path::new();
let mut shackle = PathBuilder::new();
let (cx, r) = (x + body_w / 2.0, 3.2 * k);
shackle.move_to(((cx - r) as f32, body_top as f32));
shackle.arc_to(
@@ -600,7 +602,7 @@ fn draw_lock(canvas: &Canvas, x: f64, y: f64, k: f64) {
180.0,
false,
);
canvas.draw_path(&shackle, &p);
canvas.draw_path(&shackle.detach(), &p);
}
#[cfg(test)]
+11 -11
View File
@@ -3,7 +3,7 @@
use crate::anim::{approach, ease_out_cubic};
use crate::glyphs::{hint_bar, Hint, HintKey};
use crate::theme::{fg, Fonts, PanelStroke, W};
use skia_safe::{gradient_shader, Canvas, Paint, Point, Rect, TileMode};
use skia_safe::{gradient, Canvas, Paint, Point, Rect, TileMode};
use super::{Shell, BOTTOM_BAND};
@@ -170,16 +170,16 @@ impl Shell {
// A soft pool of shade under the centre seats the text against a bright field —
// dark on a dark palette, light on a pale one, so it always separates.
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(&[
crate::theme::shade(0.5).to_color(),
crate::theme::shade(0.0).to_color(),
]),
None,
TileMode::Clamp,
None,
let shades = [crate::theme::shade(0.5), crate::theme::shade(0.0)];
vignette.set_shader(gradient::shaders::radial_gradient(
(
Point::new(cx as f32, (h / 2.0) as f32),
(w.max(h) * 0.42) as f32,
),
&gradient::Gradient::new(
gradient::Colors::new_evenly_spaced(&shades, TileMode::Clamp, None),
gradient::Interpolation::default(),
),
None,
));
canvas.draw_rect(Rect::from_wh(w as f32, h as f32), &vignette);
+16 -12
View File
@@ -236,21 +236,25 @@ impl Overlay for SkiaOverlay {
}
}
};
let backend_builder = skvk::BackendContext::new_builder(
shared.instance.handle().as_raw() as _,
shared.physical_device.as_raw() as _,
shared.device.handle().as_raw() as _,
(
shared.queue.as_raw() as _,
shared.queue_family_index as usize,
),
&get_proc,
// `None` leaves Skia's `fMaxAPIVersion` at its `0` sentinel, so it caps entry-point
// validation at whatever `vkEnumerateInstanceVersion()` reports — byte-for-byte what
// the (now removed) `BackendContext::new` did. The presenter owns the instance and its
// `VkApplicationInfo`, so pinning a version here would just duplicate its choice.
None,
);
// SAFETY: the instance/physical-device/device handles come from `shared`, which owns them
// and outlives this backend context, and `get_proc` above resolves through those same
// handles. Skia stores them but does not take ownership — teardown stays ours.
let backend = unsafe {
skvk::BackendContext::new(
shared.instance.handle().as_raw() as _,
shared.physical_device.as_raw() as _,
shared.device.handle().as_raw() as _,
(
shared.queue.as_raw() as _,
shared.queue_family_index as usize,
),
&get_proc,
)
};
let backend = unsafe { backend_builder.build() };
let mut context = gpu::direct_contexts::make_vulkan(&backend, None)
.ok_or_else(|| anyhow!("Skia DirectContext over the shared device"))?;
context.set_resource_cache_limit(RESOURCE_CACHE_BYTES);
+8 -10
View File
@@ -10,8 +10,8 @@ use skia_safe::textlayout::{
FontCollection, ParagraphBuilder, ParagraphStyle, TextAlign, TextStyle, TypefaceFontProvider,
};
use skia_safe::{
gradient_shader, Canvas, Color4f, Font, FontMgr, FontStyle, MaskFilter, Paint, PathEffect,
Point, RRect, Rect, TileMode, Typeface,
gradient, Canvas, Color4f, Font, FontMgr, FontStyle, MaskFilter, Paint, PathEffect, Point,
RRect, Rect, TileMode, Typeface,
};
// --- Ink ----------------------------------------------------------------------------------
@@ -166,18 +166,16 @@ pub(crate) fn panel(
sp.set_color4f(accent(alpha), None);
}
PanelStroke::Gradient | PanelStroke::GradientDashed => {
sp.set_shader(gradient_shader::linear(
let colors = [fg(0.22), fg(0.04)];
sp.set_shader(gradient::shaders::linear_gradient(
(
Point::new(rect.left, rect.top),
Point::new(rect.left, rect.bottom),
),
gradient_shader::GradientShaderColors::Colors(&[
fg(0.22).to_color(),
fg(0.04).to_color(),
]),
None,
TileMode::Clamp,
None,
&gradient::Gradient::new(
gradient::Colors::new_evenly_spaced(&colors, TileMode::Clamp, None),
gradient::Interpolation::default(),
),
None,
));
if matches!(stroke, PanelStroke::GradientDashed) {
+9 -9
View File
@@ -9,7 +9,7 @@ use crate::library::{BUMP_C, BUMP_K};
use crate::pointer::{Pointer, PointerKind};
use crate::theme::{accent, fg, Fonts, PanelStroke, W};
use pf_client_core::gamepad::{MenuDir, MenuEvent, MenuPulse};
use skia_safe::{Canvas, Paint, Path, RRect, Rect};
use skia_safe::{Canvas, Paint, PathBuilder, RRect, Rect};
// --- Menu list -----------------------------------------------------------------------------
@@ -479,11 +479,11 @@ fn chevron(canvas: &Canvas, x: f64, cy: f64, r: f64, left: bool, alpha: f32) {
p.set_stroke_width((1.8 * r / 4.0) as f32);
p.set_stroke_cap(skia_safe::PaintCap::Round);
p.set_anti_alias(true);
let mut path = Path::new();
let mut path = PathBuilder::new();
path.move_to(((x - dir * r / 2.0) as f32, (cy - r) as f32));
path.line_to(((x + dir * r / 2.0) as f32, cy as f32));
path.line_to(((x - dir * r / 2.0) as f32, (cy + r) as f32));
canvas.draw_path(&path, &p);
canvas.draw_path(&path.detach(), &p);
}
// --- On-screen keyboard ----------------------------------------------------------------------
@@ -785,12 +785,12 @@ fn draw_space_icon(canvas: &Canvas, cx: f64, cy: f64, k: f64, ink: skia_safe::Co
// ⎵ — an underline bracket.
let (w, h) = (16.0 * k, 5.0 * k);
let p = stroke_paint(ink, (1.6 * k) as f32);
let mut path = Path::new();
let mut path = PathBuilder::new();
path.move_to(((cx - w / 2.0) as f32, (cy - h / 2.0) as f32));
path.line_to(((cx - w / 2.0) as f32, (cy + h / 2.0) as f32));
path.line_to(((cx + w / 2.0) as f32, (cy + h / 2.0) as f32));
path.line_to(((cx + w / 2.0) as f32, (cy - h / 2.0) as f32));
canvas.draw_path(&path, &p);
canvas.draw_path(&path.detach(), &p);
}
fn draw_backspace_icon(canvas: &Canvas, cx: f64, cy: f64, k: f64, ink: skia_safe::Color4f) {
@@ -799,14 +799,14 @@ fn draw_backspace_icon(canvas: &Canvas, cx: f64, cy: f64, k: f64, ink: skia_safe
let nose = 6.0 * k;
let p = stroke_paint(ink, (1.6 * k) as f32);
let (l, r, t, b) = (cx - w / 2.0, cx + w / 2.0, cy - h / 2.0, cy + h / 2.0);
let mut path = Path::new();
let mut path = PathBuilder::new();
path.move_to(((l + nose) as f32, t as f32));
path.line_to((r as f32, t as f32));
path.line_to((r as f32, b as f32));
path.line_to(((l + nose) as f32, b as f32));
path.line_to((l as f32, cy as f32));
path.close();
canvas.draw_path(&path, &p);
canvas.draw_path(&path.detach(), &p);
let (xc, xr) = (cx + nose / 2.0, 2.6 * k);
canvas.draw_line(
((xc - xr) as f32, (cy - xr) as f32),
@@ -823,11 +823,11 @@ fn draw_backspace_icon(canvas: &Canvas, cx: f64, cy: f64, k: f64, ink: skia_safe
fn draw_check(canvas: &Canvas, cx: f64, cy: f64, k: f64, ink: skia_safe::Color4f) {
let p = stroke_paint(ink, (1.8 * k) as f32);
let r = 5.0 * k;
let mut path = Path::new();
let mut path = PathBuilder::new();
path.move_to(((cx - r) as f32, cy as f32));
path.line_to(((cx - r * 0.25) as f32, (cy + r * 0.7) as f32));
path.line_to(((cx + r) as f32, (cy - r * 0.7) as f32));
canvas.draw_path(&path, &p);
canvas.draw_path(&path.detach(), &p);
}
#[cfg(test)]