1
0
Fork 0
forked from wry/wry

it: fix integration geometry and tab scrolling

This commit is contained in:
atagen 2026-05-31 18:11:58 +10:00
parent b6502e1d8a
commit f777b4c521
30 changed files with 123 additions and 72 deletions

View file

@ -1520,25 +1520,25 @@ impl WlSurface {
let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds());
let pos = self.buffer_abs_pos.get();
let apply_damage = |pos: Rect| {
if pending.damage_full {
let mut damage = pos;
let clip_damage = |mut damage: Rect| {
damage = damage.intersect(pos);
if let Some(bounds) = bounds {
damage = damage.intersect(bounds);
}
self.client.state.damage(damage);
damage
};
if pending.damage_full {
self.client.state.damage(clip_damage(pos));
} else {
let matrix = self.damage_matrix.get();
if let Some(buffer) = self.buffer.get() {
for damage in &pending.buffer_damage {
let mut damage = matrix.apply(
let damage = matrix.apply(
pos.x1(),
pos.y1(),
damage.intersect(buffer.buffer.buf.rect),
);
if let Some(bounds) = bounds {
damage = damage.intersect(bounds);
}
self.client.state.damage(damage);
self.client.state.damage(clip_damage(damage));
}
}
for damage in &pending.surface_damage {
@ -1550,8 +1550,7 @@ impl WlSurface {
let y2 = (damage.y2() + scale - 1) / scale;
damage = Rect::new_saturating(x1, y1, x2, y2);
}
damage = damage.intersect(bounds.unwrap_or(pos));
self.client.state.damage(damage);
self.client.state.damage(clip_damage(damage));
}
}
};

View file

@ -29,6 +29,17 @@ impl TestViewport {
Ok(())
}
pub fn unset_source(&self) -> Result<(), TestError> {
self.tran.send(SetSource {
self_id: self.id,
x: Fixed::from_int(-1),
y: Fixed::from_int(-1),
width: Fixed::from_int(-1),
height: Fixed::from_int(-1),
})?;
Ok(())
}
pub fn set_destination(&self, width: i32, height: i32) -> Result<(), TestError> {
self.tran.send(SetDestination {
self_id: self.id,
@ -37,6 +48,15 @@ impl TestViewport {
})?;
Ok(())
}
pub fn unset_destination(&self) -> Result<(), TestError> {
self.tran.send(SetDestination {
self_id: self.id,
width: -1,
height: -1,
})?;
Ok(())
}
}
impl Drop for TestViewport {

View file

@ -1,7 +1,6 @@
use {
crate::{
it::{test_error::TestError, testrun::TestRun},
rect::Rect,
tree::Node,
},
std::rc::Rc,
@ -11,29 +10,19 @@ testcase!();
/// Create and map a single surface
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
run.backend.install_default()?;
let ds = run.create_default_setup().await?;
let client = run.create_client().await?;
let window = client.create_window().await?;
window.map().await?;
tassert_eq!(window.tl.core.width.get(), 800);
tassert_eq!(
window.tl.core.height.get(),
600 - 2 * run.state.theme.title_plus_underline_height()
);
let workspace_rect = ds.output.workspace_rect.get();
tassert_eq!(
window.tl.server.node_absolute_position(),
Rect::new_sized(
0,
2 * run.state.theme.title_plus_underline_height(),
window.tl.core.width.get(),
window.tl.core.height.get(),
)
.unwrap()
);
tassert_eq!(window.tl.core.width.get(), workspace_rect.width());
tassert_eq!(window.tl.core.height.get(), workspace_rect.height());
tassert_eq!(window.tl.server.node_absolute_position(), workspace_rect);
Ok(())
}

View file

@ -11,7 +11,7 @@ testcase!();
/// Create and map two surfaces
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
run.backend.install_default()?;
let ds = run.create_default_setup().await?;
let client = run.create_client().await?;
@ -21,17 +21,30 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let window2 = client.create_window().await?;
window2.map().await?;
let otop = 2 * run.state.theme.title_plus_underline_height();
let workspace_rect = ds.output.workspace_rect.get();
let bw = run.state.theme.sizes.border_width.get();
let child_width = (workspace_rect.width() - bw) / 2;
tassert_eq!(
window.tl.server.node_absolute_position(),
Rect::new_sized(0, otop, (800 - bw) / 2, 600 - otop).unwrap()
Rect::new_sized(
workspace_rect.x1(),
workspace_rect.y1(),
child_width,
workspace_rect.height(),
)
.unwrap()
);
tassert_eq!(
window2.tl.server.node_absolute_position(),
Rect::new_sized((800 - bw) / 2 + bw, otop, (800 - bw) / 2, 600 - otop).unwrap()
Rect::new_sized(
workspace_rect.x1() + child_width + bw,
workspace_rect.y1(),
child_width,
workspace_rect.height(),
)
.unwrap()
);
Ok(())

View file

@ -48,13 +48,18 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let mono_container = w_mono2.tl.container_parent()?;
let container_pos = mono_container.tl_data().pos.get();
let w_mono1_title = mono_container.render_data.borrow_mut().title_rects[0]
.move_(container_pos.x1(), container_pos.y1());
ds.mouse.abs(
&ds.connector,
w_mono1_title.x1() as _,
w_mono1_title.y1() as _,
);
let (tab_x, tab_y) = {
let tab_bar = mono_container.tab_bar.borrow();
let Some(tab_bar) = tab_bar.as_ref() else {
bail!("no tab bar");
};
let w_mono1_title = &tab_bar.entries[0];
(
container_pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2,
container_pos.y1() + tab_bar.height / 2,
)
};
ds.mouse.abs(&ds.connector, tab_x as _, tab_y as _);
client.sync().await;
tassert!(enters.next().is_err());

View file

@ -26,12 +26,18 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let container = w_mono2.tl.container_parent()?;
let pos = container.tl_data().pos.get();
let w_mono1_title = container.render_data.borrow_mut().title_rects[0].move_(pos.x1(), pos.y1());
ds.mouse.abs(
&ds.connector,
w_mono1_title.x1() as f64,
w_mono1_title.y1() as f64,
);
let (tab_x, tab_y) = {
let tab_bar = container.tab_bar.borrow();
let Some(tab_bar) = tab_bar.as_ref() else {
bail!("no tab bar");
};
let w_mono1_title = &tab_bar.entries[0];
(
pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2,
pos.y1() + tab_bar.height / 2,
)
};
ds.mouse.abs(&ds.connector, tab_x as f64, tab_y as f64);
client.sync().await;
let enters = dss.kb.enter.expect()?;

View file

@ -308,9 +308,8 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let output_damage = connector_data.damage.borrow();
tassert!(!output_damage.is_empty());
// Buffer damage is transformed by the damage matrix which includes the surface position
// The buffer damage (0,0,1,1) should be transformed to surface coordinates
let expected_buffer_damage = buffer_damage.move_(surface_pos.x1(), surface_pos.y1());
// The test window maps its 1x1 buffer through a viewport to the full window size.
let expected_buffer_damage = surface_pos;
// Find the exact output damage that matches our expected buffer damage
let mut found_exact_buffer_damage = false;
@ -331,10 +330,12 @@ async fn test(run: Rc<TestRun>) -> TestResult {
// Test 7: Check output damage from existing window's viewport (which already has scaling)
connector_data.damage.borrow_mut().clear();
// The existing window was created with create_surface_ext() which automatically creates a viewport
// Let's verify that the viewport's existing scaling affects buffer damage correctly
// First, let's modify the viewport scaling that already exists on the window
window.surface.viewport.set_destination(150, 100)?; // Change scaling to 150x100
// The existing window was created with create_surface_ext() which automatically creates a viewport.
// Commit the viewport size change separately; that commit intentionally damages the old/new extents.
window.surface.viewport.set_destination(150, 100)?;
window.surface.commit()?;
client.sync().await;
connector_data.damage.borrow_mut().clear();
// Add buffer damage to test viewport scaling coordinate transformation
window.surface.damage_buffer(0, 0, 1, 1)?; // Damage entire 1x1 buffer
@ -346,8 +347,8 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let output_damage = connector_data.damage.borrow();
tassert!(!output_damage.is_empty());
// With viewporter scaling, the 1x1 buffer damage should scale to 150x100
// and be moved by surface position (0, 36) to get output coordinates (0, 36, 150, 136)
// With viewporter scaling, the 1x1 buffer damage should scale to the viewport destination.
let surface_pos = window.surface.server.buffer_abs_pos.get();
let expected_scaled_damage = Rect::new_sized(0, 0, 150, 100).unwrap();
let expected_output_damage =
expected_scaled_damage.move_(surface_pos.x1(), surface_pos.y1());
@ -402,8 +403,9 @@ async fn test(run: Rc<TestRun>) -> TestResult {
rotation_window.map().await?;
client.sync().await;
// Disable viewporter by setting destination to 0x0 to rely purely on buffer dimensions
rotation_window.surface.viewport.set_destination(0, 0)?; // Disable viewporter
// Disable viewporter to rely purely on buffer dimensions.
rotation_window.surface.viewport.unset_source()?;
rotation_window.surface.viewport.unset_destination()?;
// Use a rectangular buffer (4x2) so rotation has a visible geometric effect
// Attach AFTER mapping to avoid being overwritten by map()'s single-pixel buffer

View file

@ -32,6 +32,7 @@ use {
numcell::NumCell,
on_drop_event::OnDropEvent,
rc_eq::rc_eq,
scroller::Scroller,
threshold_counter::ThresholdCounter,
},
},
@ -150,6 +151,7 @@ pub struct ContainerNode {
pub child_removed: Rc<LazyEventSource>,
pub all_children_resized: Rc<LazyEventSource>,
pub tab_bar: RefCell<Option<TabBar>>,
scroll: Scroller,
pub update_tab_textures_scheduled: Cell<bool>,
pub ephemeral: Cell<Ephemeral>,
}
@ -266,6 +268,7 @@ impl ContainerNode {
child_removed: state.lazy_event_sources.create_source(),
all_children_resized: state.post_layout_event_sources.create_source(),
tab_bar: RefCell::new(None),
scroll: Default::default(),
update_tab_textures_scheduled: Cell::new(false),
ephemeral: Cell::new(Ephemeral::Off),
});
@ -793,6 +796,18 @@ impl ContainerNode {
self.activate_child2(child, false);
}
fn activate_child_from_input(
self: &Rc<Self>,
child: &NodeRef<ContainerChild>,
seat: &Rc<WlSeatGlobal>,
) {
self.activate_child(child);
child
.node
.clone()
.node_do_focus(seat, Direction::Unspecified);
}
fn activate_child2(self: &Rc<Self>, child: &NodeRef<ContainerChild>, preserve_focus: bool) {
if let Some(mc) = self.mono_child.get() {
if mc.node.node_id() == child.node.node_id() {
@ -1519,7 +1534,7 @@ impl ContainerNode {
fn button(
self: Rc<Self>,
id: CursorType,
_seat: &Rc<WlSeatGlobal>,
seat: &Rc<WlSeatGlobal>,
_time_usec: u64,
pressed: bool,
button: u32,
@ -1549,7 +1564,7 @@ impl ContainerNode {
if let Some(child) = children.get(&child_id) {
let child_ref = child.to_ref();
drop(children);
self.activate_child(&child_ref);
self.activate_child_from_input(&child_ref, seat);
}
return;
}
@ -2066,31 +2081,33 @@ impl Node for ContainerNode {
self.button(id, seat, time_usec, state == ButtonState::Pressed, button);
}
fn node_on_axis_event(self: Rc<Self>, _seat: &Rc<WlSeatGlobal>, event: &PendingScroll) {
fn node_on_axis_event(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, event: &PendingScroll) {
if self.mono_child.is_none() {
return;
}
// Use vertical scroll (index 1) to switch tabs.
let v = match event.v120[1].get() {
Some(v) if v != 0 => v,
let steps = match self.scroll.handle(event) {
Some(steps) => steps,
_ => return,
};
let mono = match self.mono_child.get() {
let mut target = match self.mono_child.get() {
Some(m) => m,
None => return,
};
let next = if v > 0 {
// Scroll down → next tab.
mono.next().or_else(|| self.children.first())
} else {
// Scroll up → previous tab.
mono.prev().or_else(|| self.children.last())
};
if let Some(next) = next {
if next.node.node_id() != mono.node.node_id() {
self.activate_child(&next);
let current_id = target.node.node_id();
for _ in 0..steps.abs() {
let next = if steps > 0 {
target.next().or_else(|| self.children.first())
} else {
target.prev().or_else(|| self.children.last())
};
match next {
Some(next) => target = next,
None => break,
}
}
if target.node.node_id() != current_id {
self.activate_child_from_input(&target, seat);
}
}
fn node_on_leave(&self, seat: &WlSeatGlobal) {