🎉 Phase 6.3 — display + input + compositor-core intégrés runtime
Captures preuves dans docs/phase6-3-*.png : 4 frames qui prouvent
visuellement que raise change l'ordre Z et que compose_into propage le
résultat à l'écran QEMU :
- default-z.png : 3 surfaces overlap, blue top (créé en dernier)
- red-top.png : sendkey 1 → raise(red) → red couvre vert et bleu
- green-top.png : sendkey 2 → raise(green) → green couvre tout dans sa zone
- blue-top.png : sendkey 3 → raise(blue) → retour visuel à initial
Modifications :
compositor-core (commit dbf3bff → maintenant) :
- + iter_z_order_front_to_back() : utile pour hit testing
- + hit_test(x, y) -> Option<SurfaceId> : trouve la surface visible la
plus haute qui contient le point
- + 4 tests unitaires : 27 total / 27 pass natif (0.00s)
redox-wl-display :
- + dep redox-wl-compositor-core
- + impl Framebuffer for RedoxOutput (délègue à pixels_mut + width/height)
bin redox-wl-test-compose-static (190 lignes) :
- ouvre RedoxOutput + take_crtc
- crée InputBackend partagé
- 3 surfaces ARGB unies (rouge/vert/bleu) avec overlap centré
- boucle event : '1'/'2'/'3' raise resp. red/green/blue
- clic souris → hit_test puis raise (motion non testé sans usb-tablet)
- ré-render seulement si raise → économie CPU
- present_with_takeover() à chaque iter pour tenir le CRTC
Validation QEMU automatisée : sendkey 1/2/3 + screendump entre chaque.
Les 4 PNG montrent l'ordre Z évoluer correctement.
Image Redox restaurée à boot Orbital normal.
docs/phase6-compositor-core.md : compte-rendu 6.1-6.3 complet,
architecture, dépendances, API, limitations, plan 6.4.
Phase 6.3 close. Reste 6.4 (frontend Wayland : wl_compositor + wl_shm
+ xdg-shell mappés vers compositor-core, damage tracking, frame
callbacks). Estimé 2-3 sessions.
Leyoda 2026 – GPLv3
This commit is contained in:
parent
dbf3bffa2b
commit
509aae7769
10 changed files with 608 additions and 0 deletions
|
|
@ -192,6 +192,40 @@ impl SurfaceRegistry {
|
|||
.filter_map(|id| self.surfaces.get(id))
|
||||
}
|
||||
|
||||
/// Itérer les surfaces dans l'ordre Z, du premier plan vers le fond.
|
||||
/// Utile pour le hit testing : on cherche la première surface
|
||||
/// (la plus haute) qui contient le point.
|
||||
pub fn iter_z_order_front_to_back(&self) -> impl Iterator<Item = &Surface> {
|
||||
self.z_order
|
||||
.iter()
|
||||
.rev()
|
||||
.filter_map(|id| self.surfaces.get(id))
|
||||
}
|
||||
|
||||
/// Trouve la surface visible la plus haute qui contient le point `(x, y)`
|
||||
/// dans son rect [state.x..state.x+buffer.width) × [state.y..state.y+buffer.height).
|
||||
/// Retourne `None` si aucune surface ne couvre ce point.
|
||||
/// Surfaces invisibles ou sans buffer sont ignorées.
|
||||
pub fn hit_test(&self, x: i32, y: i32) -> Option<SurfaceId> {
|
||||
for surface in self.iter_z_order_front_to_back() {
|
||||
let s = surface.current();
|
||||
if !s.visible {
|
||||
continue;
|
||||
}
|
||||
let Some(buf) = &s.buffer else {
|
||||
continue;
|
||||
};
|
||||
let x0 = s.x;
|
||||
let y0 = s.y;
|
||||
let x1 = x0.saturating_add(buf.width as i32);
|
||||
let y1 = y0.saturating_add(buf.height as i32);
|
||||
if x >= x0 && x < x1 && y >= y0 && y < y1 {
|
||||
return Some(surface.id());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.surfaces.len()
|
||||
}
|
||||
|
|
@ -708,6 +742,73 @@ mod tests {
|
|||
assert!(fb2.pixels.iter().all(|&p| p == 0xFF00FF00));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_z_order_front_to_back_is_reverse() {
|
||||
let mut r = SurfaceRegistry::new();
|
||||
let a = r.create();
|
||||
let b = r.create();
|
||||
let c = r.create();
|
||||
let order: Vec<SurfaceId> = r
|
||||
.iter_z_order_front_to_back()
|
||||
.map(|s| s.id())
|
||||
.collect();
|
||||
assert_eq!(order, vec![c, b, a]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hit_test_returns_topmost_surface_at_point() {
|
||||
let mut r = SurfaceRegistry::new();
|
||||
// bg : 100x100 à (0, 0)
|
||||
let bg = add_surface(&mut r, 0, 0, 100, 100, 0xFFFF0000);
|
||||
// overlay : 30x30 à (10, 10)
|
||||
let overlay = add_surface(&mut r, 10, 10, 30, 30, 0xFF0000FF);
|
||||
|
||||
// Point (5, 5) : dans bg uniquement
|
||||
assert_eq!(r.hit_test(5, 5), Some(bg));
|
||||
// Point (20, 20) : dans les deux, overlay au-dessus → overlay
|
||||
assert_eq!(r.hit_test(20, 20), Some(overlay));
|
||||
// Point (50, 50) : dans bg seulement
|
||||
assert_eq!(r.hit_test(50, 50), Some(bg));
|
||||
// Point (200, 200) : hors écran
|
||||
assert_eq!(r.hit_test(200, 200), None);
|
||||
// Point (-1, -1) : hors écran (négatif)
|
||||
assert_eq!(r.hit_test(-1, -1), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hit_test_skips_invisible_and_no_buffer() {
|
||||
let mut r = SurfaceRegistry::new();
|
||||
let a = add_surface(&mut r, 0, 0, 100, 100, 0xFFFF0000);
|
||||
let b = add_surface(&mut r, 0, 0, 100, 100, 0xFF00FF00);
|
||||
// Cache b → hit_test au-dessus de la zone doit retourner a
|
||||
r.modify_pending(b, |s| s.visible = false);
|
||||
r.commit(b);
|
||||
assert_eq!(r.hit_test(50, 50), Some(a));
|
||||
|
||||
// c sans buffer
|
||||
let c = r.create();
|
||||
r.modify_pending(c, |s| {
|
||||
s.x = 0;
|
||||
s.y = 0;
|
||||
s.visible = true;
|
||||
});
|
||||
r.commit(c);
|
||||
// toujours a (c n'a pas de buffer donc skip)
|
||||
assert_eq!(r.hit_test(50, 50), Some(a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hit_test_after_raise_returns_raised_surface() {
|
||||
let mut r = SurfaceRegistry::new();
|
||||
let a = add_surface(&mut r, 0, 0, 100, 100, 0xFFFF0000);
|
||||
let b = add_surface(&mut r, 0, 0, 100, 100, 0xFF00FF00);
|
||||
// b est au-dessus initialement
|
||||
assert_eq!(r.hit_test(50, 50), Some(b));
|
||||
r.raise(a);
|
||||
// a passe au-dessus
|
||||
assert_eq!(r.hit_test(50, 50), Some(a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typical_compositor_workflow() {
|
||||
// Scénario : 3 fenêtres, on les modifie + commit, on raise une au top
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue