Library changes outside the platform's own dependencies (those came with the platform commits above). libs/asset and libs/flow move to the Stage repository with the apps that use them. Covers work ebf442a94..2b1a41df4 for libs: - wm: the Android super-app compiles tiles on the phone against the host engine - cargo-makepad: the super-app APK is packed by `android dyn-pack`, in Rust - cef: a page's audio can be captured instead of played - flow: the flow-ui app and the flowgraph canvas return to the workspace, with staged progress in libs/flow - svg: a stroke that bends tighter than its half width collapses its inner edge to the corner - the accent a name is written with stops deciding whether search finds it - ai stems: LaneDemixer, VocalsModel and the platform Stage origin/main needs - The regex engine accepts \b and \B, reports the earliest match so a scan can resume after it, and can decide word boundaries on ASCII so a code sear - workspace: Scope's settings carry the code map's tab width and the directories each prepared project hides from its map - cef, video, platform: what Stage's browser needs from the shared layers - platform, wm: work builds again on iOS, tvOS and wasm, and the window manager is warning-free on Windows - ci: tools/ci, a Splash runtime that watches a branch and runs every ci.splash it finds - stitch: the crate says what the rest of the repository says about its license - ci: a run is one target dir and one cargo batch per target, and the wall draws - apps: what the CI box found - Remove Flow, asset and VJ applications moved into Stage - tests: the workspace suite, run as a whole for the first time, passes outside the example UI tests - libs: no warnings in the workspace check on any row - tests: the last three failures of the CI box's night - gif: the crate's doc examples compile - cef: a hosted browser survives its host's exit - tests: the three binaries over ten seconds in release come under it - git: the memory ledger can take what it is asked for - script: re-entrant dispatch, thread index validation, Any-based handle downcasts, UTF-8 previews; regex: never_loop fix - fix(text): enable ttf-parser gvar-alloc for many-tuple variable fonts - platform, widgets, wm: a touch that is taken away is cancelled, never released - platform, wm: hosted apps ask the WM for what they cannot do themselves - wm on Android: apps build on the phone - task, cargo-makepad, widgets, audio: the task manager chooses its columns, graphs every process' network and disk traffic and installs itself as a D - flowgraph: ordered ports take many wires, sockets can override their icon, and the canvas embeds as a viewport - cef: captured frames and audio carry callback clocks and a navigation epoch - git: timings read a portable clock - ai: the hub's rig-fixture tests find the asset library without the asset client - ai llm: a Metal main buffer is filled in 32 MB chunks - diskmap: a scan lists each macOS directory in one getattrlistbulk call and classifies files without allocating or locking - platform, widgets, audio_route, ai: window crossfades and whole-frame presents, caption controls that click, themed menus, a stereo-pair audio tap, - builder: the Builder TUI matches the new terminal design, Windows executables carry app icons, and every app has one - sheets, score, git, home: saves cannot cut a file short or overwrite another one, and checkouts cannot write outside the repository - git: worktree status matches git on real repositories and can be cancelled - cuda: builds link only the CUDA toolkit they are given, never a system install - audio_route: one API on macOS, Windows and Linux, monitoring by default and processing only when asked, plus shaders that discard a value compile to - platform, widgets, ai: a restyle recompiles nothing it already has, shows only complete frames, and a window can own its caption - audio_route example: print the samples that reached the processor each second - widgets, builder: the big widget families are features, all on by default, and the Builder takes none of them - ai hub: a local chat with no model says so once, plainly - ai: Claude Desktop drives any app with the F10 panel - wm: children open no console windows on Windows - ai: Connect hands the .mcpb to Claude Desktop itself - civil-time: one local wall clock for every app, and it knows the zone on Windows - ai-speech: makepad-ai-sfx (and with it ai-h3) is built only for IndexTTS - script + wasm_bridge: no build script that reruns on every file - build: the AI stack, csg, Scope and Source Library join the one workspace - ai chat: the chat's backends are the CLI, MCP and cloud providers; the local model is the `localai` feature - widgets: every widget family is a crate of its own under widgets/families, and makepad-widgets links, re-exports and registers the ones its features - apps, examples, libs: each crate builds only the widget families it uses, and the design overlay is each app's own default feature - livepipe: takes makepad-widgets without its default families, now that the AI stack is in the one workspace - ai chat: a build without local AI starts on the first provider that answers here, not on "No model" - thiserror: RUSTC_BOOTSTRAP no longer turns on the unstable generic member access API - builder: makepad-builder.exe declares itself like a well-formed Windows program and stops a stalled build through a job object instead of taskkill - builder + ai-cuda: the CUDA kernel progress shows in the Builder's Compile row, not scribbled over its screen - repo: no third-party comic archive, generated models or sample asset in the tree - builder: the Windows Builder ships as source, runs in its own console, and apps compile with Rust's GNU toolchain or Microsoft's - win_resource: PNG decode and encode on makepad-fast-inflate instead of zune-png - builder: one folder layout everywhere, makepad-builder.zip with only the files a Windows build reads, a Rust download over parallel connections, and Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
226 lines
7.9 KiB
Rust
226 lines
7.9 KiB
Rust
use makepad_flowgraph::wire_route::{
|
|
obstacles_in_corridor, route_wire, route_wire_sticky, Obstacle, Point, PortSide, RouteKind,
|
|
RouteStyle, WireRoute,
|
|
};
|
|
|
|
const CLEARANCE: f64 = 12.0;
|
|
|
|
fn screenshot_cards(prompt_y: f64) -> [Obstacle; 5] {
|
|
[
|
|
Obstacle::from_xywh(-1359.0, prompt_y, 290.0, 150.0),
|
|
Obstacle::from_xywh(-955.0, -422.0, 290.0, 200.0),
|
|
Obstacle::from_xywh(-552.0, -554.0, 290.0, 200.0),
|
|
Obstacle::from_xywh(-567.0, -885.0, 290.0, 200.0),
|
|
Obstacle::from_xywh(-1210.0, -966.0, 290.0, 150.0),
|
|
]
|
|
}
|
|
|
|
fn assert_port_directions(route: &WireRoute, source: PortSide, target: PortSide) {
|
|
let RouteKind::Orthogonal { points, .. } = &route.kind else {
|
|
panic!("expected orthogonal geometry");
|
|
};
|
|
let sign = |side| if side == PortSide::Right { 1.0 } else { -1.0 };
|
|
assert_eq!(points[1].y, route.from.y, "{:?}", route.kind);
|
|
assert_eq!((points[1].x - route.from.x).signum(), sign(source));
|
|
let last = points[points.len() - 2];
|
|
assert_eq!(last.y, route.to.y, "{:?}", route.kind);
|
|
assert_eq!((last.x - route.to.x).signum(), sign(target));
|
|
}
|
|
|
|
fn assert_short(route: &WireRoute) {
|
|
let manhattan = (route.to.x - route.from.x).abs() + (route.to.y - route.from.y).abs();
|
|
assert!(
|
|
route.length() <= manhattan + 2.0 * CLEARANCE,
|
|
"length={} manhattan={manhattan} {:?}",
|
|
route.length(),
|
|
route.kind
|
|
);
|
|
}
|
|
|
|
fn assert_outside_cards(route: &WireRoute, cards: &[Obstacle]) {
|
|
for step in 0..=(route.length().ceil() as usize * 4) {
|
|
let point = route.point_at_distance(step as f64 * 0.25);
|
|
for card in cards {
|
|
assert!(
|
|
point.x <= card.min.x
|
|
|| point.x >= card.max.x
|
|
|| point.y <= card.min.y
|
|
|| point.y >= card.max.y,
|
|
"point={point:?} card={card:?} {:?}",
|
|
route.kind
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn screenshot_fixture(mirror_y: bool, target_side: PortSide) {
|
|
// Test both card-edge ports and the actual canvas anchor offsets:
|
|
// output tip = 13 + 4.5; input notch = 13 - 3; first port row = 26.
|
|
for (source_offset, target_offset) in [(0.0, 0.0), (17.5, 10.0)] {
|
|
let mut cards = screenshot_cards(30.0);
|
|
let from = Point::new(cards[0].max.x + source_offset, cards[0].min.y + 26.0);
|
|
let target_x = if target_side == PortSide::Left {
|
|
cards[1].min.x - target_offset
|
|
} else {
|
|
cards[1].max.x + target_offset
|
|
};
|
|
let to = Point::new(target_x, cards[1].min.y + 26.0);
|
|
let reflect = |p: Point| Point::new(p.x, if mirror_y { -p.y } else { p.y });
|
|
let from = reflect(from);
|
|
let to = reflect(to);
|
|
if mirror_y {
|
|
for card in &mut cards {
|
|
let (min, max) = (reflect(card.max), reflect(card.min));
|
|
card.min.y = min.y;
|
|
card.max.y = max.y;
|
|
}
|
|
}
|
|
let obstacles: Vec<_> = cards.iter().map(|card| card.inflate(CLEARANCE)).collect();
|
|
// Auto-flip/preview use every card; per-frame drawing uses the corridor.
|
|
let style = RouteStyle::default();
|
|
let local = obstacles_in_corridor(
|
|
from,
|
|
to,
|
|
&obstacles,
|
|
style.port_stub + style.corner_radius * 2.0,
|
|
);
|
|
for obstacles in [&obstacles, &local] {
|
|
let route = route_wire(
|
|
from,
|
|
PortSide::Right,
|
|
to,
|
|
target_side,
|
|
obstacles,
|
|
style,
|
|
0.0,
|
|
);
|
|
assert_short(&route);
|
|
assert_port_directions(&route, PortSide::Right, target_side);
|
|
assert_outside_cards(&route, &cards);
|
|
for point in route.slice(0.0, route.length()) {
|
|
assert!(
|
|
if mirror_y {
|
|
point.y <= cards[1].max.y
|
|
} else {
|
|
point.y >= cards[1].min.y
|
|
},
|
|
"{:?}",
|
|
route.kind
|
|
);
|
|
let max_x = if target_side == PortSide::Left {
|
|
cards[1].max.x
|
|
} else {
|
|
to.x + CLEARANCE
|
|
};
|
|
assert!(point.x <= max_x, "{:?}", route.kind);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn screenshot_prompt_below_left_of_expand_does_not_wrap() {
|
|
screenshot_fixture(false, PortSide::Left);
|
|
}
|
|
|
|
#[test]
|
|
fn screenshot_mirrored_prompt_above_left_of_expand_does_not_wrap() {
|
|
screenshot_fixture(true, PortSide::Left);
|
|
}
|
|
|
|
#[test]
|
|
fn screenshot_flipped_expand_uses_only_its_near_corner() {
|
|
screenshot_fixture(false, PortSide::Right);
|
|
}
|
|
|
|
#[test]
|
|
fn screenshot_expand_to_add_style_stays_between_its_ports() {
|
|
let cards = screenshot_cards(30.0);
|
|
let obstacles: Vec<_> = cards.iter().map(|card| card.inflate(CLEARANCE)).collect();
|
|
let from = Point::new(cards[1].max.x + 17.5, cards[1].min.y + 26.0);
|
|
let to = Point::new(cards[2].min.x - 10.0, cards[2].min.y + 26.0);
|
|
let route = route_wire(
|
|
from,
|
|
PortSide::Right,
|
|
to,
|
|
PortSide::Left,
|
|
&obstacles,
|
|
RouteStyle::default(),
|
|
0.0,
|
|
);
|
|
assert_short(&route);
|
|
assert_port_directions(&route, PortSide::Right, PortSide::Left);
|
|
assert_outside_cards(&route, &cards);
|
|
assert!(
|
|
route.slice(0.0, route.length()).iter().all(|point| {
|
|
point.x >= from.x && point.x <= to.x && point.y >= to.y && point.y <= from.y
|
|
}),
|
|
"{:?}",
|
|
route.kind
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn nearly_level_ports_keep_short_fillets_and_card_clearance() {
|
|
for dy in [-31.0, -16.0, -4.0, -0.5, 0.5, 4.0, 16.0, 31.0] {
|
|
for offset in [-4.0, 0.0, 4.0] {
|
|
let cards = [
|
|
Obstacle::from_xywh(0.0, 0.0, 290.0, 200.0),
|
|
Obstacle::from_xywh(404.0, dy, 290.0, 200.0),
|
|
];
|
|
let obstacles: Vec<_> = cards.iter().map(|card| card.inflate(CLEARANCE)).collect();
|
|
let route = route_wire(
|
|
Point::new(290.0, 26.0),
|
|
PortSide::Right,
|
|
Point::new(404.0, 26.0 + dy),
|
|
PortSide::Left,
|
|
&obstacles,
|
|
RouteStyle::default(),
|
|
offset,
|
|
);
|
|
assert_short(&route);
|
|
assert_port_directions(&route, PortSide::Right, PortSide::Left);
|
|
assert_outside_cards(&route, &cards);
|
|
let RouteKind::Orthogonal { points, .. } = &route.kind else {
|
|
unreachable!();
|
|
};
|
|
assert_eq!(points.len(), 4, "{:?}", route.kind);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn screenshot_drag_from_saved_position_never_takes_an_outside_row() {
|
|
for (source_offset, target_offset) in [(0.0, 0.0), (17.5, 10.0)] {
|
|
let mut previous = None;
|
|
for y in -426..=30 {
|
|
let cards = screenshot_cards(y as f64);
|
|
let obstacles: Vec<_> = cards.iter().map(|card| card.inflate(CLEARANCE)).collect();
|
|
let from = Point::new(cards[0].max.x + source_offset, cards[0].min.y + 26.0);
|
|
let to = Point::new(cards[1].min.x - target_offset, cards[1].min.y + 26.0);
|
|
let route = route_wire_sticky(
|
|
from,
|
|
PortSide::Right,
|
|
to,
|
|
PortSide::Left,
|
|
&obstacles,
|
|
RouteStyle::default(),
|
|
0.0,
|
|
previous.as_ref(),
|
|
);
|
|
assert_short(&route);
|
|
assert_port_directions(&route, PortSide::Right, PortSide::Left);
|
|
assert_outside_cards(&route, &cards);
|
|
assert!(
|
|
route.slice(0.0, route.length()).iter().all(|point| {
|
|
point.y >= from.y.min(to.y)
|
|
&& point.y <= from.y.max(to.y)
|
|
&& point.x <= cards[1].max.x
|
|
}),
|
|
"y={y} {:?}",
|
|
route.kind
|
|
);
|
|
previous = Some(route);
|
|
}
|
|
}
|
|
}
|