it: fix integration geometry and tab scrolling
This commit is contained in:
parent
b6502e1d8a
commit
f777b4c521
30 changed files with 123 additions and 72 deletions
|
|
@ -1520,25 +1520,25 @@ impl WlSurface {
|
||||||
let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds());
|
let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds());
|
||||||
let pos = self.buffer_abs_pos.get();
|
let pos = self.buffer_abs_pos.get();
|
||||||
let apply_damage = |pos: Rect| {
|
let apply_damage = |pos: Rect| {
|
||||||
if pending.damage_full {
|
let clip_damage = |mut damage: Rect| {
|
||||||
let mut damage = pos;
|
damage = damage.intersect(pos);
|
||||||
if let Some(bounds) = bounds {
|
if let Some(bounds) = bounds {
|
||||||
damage = damage.intersect(bounds);
|
damage = damage.intersect(bounds);
|
||||||
}
|
}
|
||||||
self.client.state.damage(damage);
|
damage
|
||||||
|
};
|
||||||
|
if pending.damage_full {
|
||||||
|
self.client.state.damage(clip_damage(pos));
|
||||||
} else {
|
} else {
|
||||||
let matrix = self.damage_matrix.get();
|
let matrix = self.damage_matrix.get();
|
||||||
if let Some(buffer) = self.buffer.get() {
|
if let Some(buffer) = self.buffer.get() {
|
||||||
for damage in &pending.buffer_damage {
|
for damage in &pending.buffer_damage {
|
||||||
let mut damage = matrix.apply(
|
let damage = matrix.apply(
|
||||||
pos.x1(),
|
pos.x1(),
|
||||||
pos.y1(),
|
pos.y1(),
|
||||||
damage.intersect(buffer.buffer.buf.rect),
|
damage.intersect(buffer.buffer.buf.rect),
|
||||||
);
|
);
|
||||||
if let Some(bounds) = bounds {
|
self.client.state.damage(clip_damage(damage));
|
||||||
damage = damage.intersect(bounds);
|
|
||||||
}
|
|
||||||
self.client.state.damage(damage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for damage in &pending.surface_damage {
|
for damage in &pending.surface_damage {
|
||||||
|
|
@ -1550,8 +1550,7 @@ impl WlSurface {
|
||||||
let y2 = (damage.y2() + scale - 1) / scale;
|
let y2 = (damage.y2() + scale - 1) / scale;
|
||||||
damage = Rect::new_saturating(x1, y1, x2, y2);
|
damage = Rect::new_saturating(x1, y1, x2, y2);
|
||||||
}
|
}
|
||||||
damage = damage.intersect(bounds.unwrap_or(pos));
|
self.client.state.damage(clip_damage(damage));
|
||||||
self.client.state.damage(damage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,17 @@ impl TestViewport {
|
||||||
Ok(())
|
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> {
|
pub fn set_destination(&self, width: i32, height: i32) -> Result<(), TestError> {
|
||||||
self.tran.send(SetDestination {
|
self.tran.send(SetDestination {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
|
|
@ -37,6 +48,15 @@ impl TestViewport {
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
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 {
|
impl Drop for TestViewport {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
it::{test_error::TestError, testrun::TestRun},
|
it::{test_error::TestError, testrun::TestRun},
|
||||||
rect::Rect,
|
|
||||||
tree::Node,
|
tree::Node,
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
|
|
@ -11,29 +10,19 @@ testcase!();
|
||||||
|
|
||||||
/// Create and map a single surface
|
/// Create and map a single surface
|
||||||
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
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 client = run.create_client().await?;
|
||||||
|
|
||||||
let window = client.create_window().await?;
|
let window = client.create_window().await?;
|
||||||
window.map().await?;
|
window.map().await?;
|
||||||
|
|
||||||
tassert_eq!(window.tl.core.width.get(), 800);
|
let workspace_rect = ds.output.workspace_rect.get();
|
||||||
tassert_eq!(
|
|
||||||
window.tl.core.height.get(),
|
|
||||||
600 - 2 * run.state.theme.title_plus_underline_height()
|
|
||||||
);
|
|
||||||
|
|
||||||
tassert_eq!(
|
tassert_eq!(window.tl.core.width.get(), workspace_rect.width());
|
||||||
window.tl.server.node_absolute_position(),
|
tassert_eq!(window.tl.core.height.get(), workspace_rect.height());
|
||||||
Rect::new_sized(
|
|
||||||
0,
|
tassert_eq!(window.tl.server.node_absolute_position(), workspace_rect);
|
||||||
2 * run.state.theme.title_plus_underline_height(),
|
|
||||||
window.tl.core.width.get(),
|
|
||||||
window.tl.core.height.get(),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ testcase!();
|
||||||
|
|
||||||
/// Create and map two surfaces
|
/// Create and map two surfaces
|
||||||
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
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 client = run.create_client().await?;
|
||||||
|
|
||||||
|
|
@ -21,17 +21,30 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
||||||
let window2 = client.create_window().await?;
|
let window2 = client.create_window().await?;
|
||||||
window2.map().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 bw = run.state.theme.sizes.border_width.get();
|
||||||
|
let child_width = (workspace_rect.width() - bw) / 2;
|
||||||
|
|
||||||
tassert_eq!(
|
tassert_eq!(
|
||||||
window.tl.server.node_absolute_position(),
|
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!(
|
tassert_eq!(
|
||||||
window2.tl.server.node_absolute_position(),
|
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(())
|
Ok(())
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -48,13 +48,18 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
|
|
||||||
let mono_container = w_mono2.tl.container_parent()?;
|
let mono_container = w_mono2.tl.container_parent()?;
|
||||||
let container_pos = mono_container.tl_data().pos.get();
|
let container_pos = mono_container.tl_data().pos.get();
|
||||||
let w_mono1_title = mono_container.render_data.borrow_mut().title_rects[0]
|
let (tab_x, tab_y) = {
|
||||||
.move_(container_pos.x1(), container_pos.y1());
|
let tab_bar = mono_container.tab_bar.borrow();
|
||||||
ds.mouse.abs(
|
let Some(tab_bar) = tab_bar.as_ref() else {
|
||||||
&ds.connector,
|
bail!("no tab bar");
|
||||||
w_mono1_title.x1() as _,
|
};
|
||||||
w_mono1_title.y1() as _,
|
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;
|
client.sync().await;
|
||||||
tassert!(enters.next().is_err());
|
tassert!(enters.next().is_err());
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,18 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
|
|
||||||
let container = w_mono2.tl.container_parent()?;
|
let container = w_mono2.tl.container_parent()?;
|
||||||
let pos = container.tl_data().pos.get();
|
let pos = container.tl_data().pos.get();
|
||||||
let w_mono1_title = container.render_data.borrow_mut().title_rects[0].move_(pos.x1(), pos.y1());
|
let (tab_x, tab_y) = {
|
||||||
ds.mouse.abs(
|
let tab_bar = container.tab_bar.borrow();
|
||||||
&ds.connector,
|
let Some(tab_bar) = tab_bar.as_ref() else {
|
||||||
w_mono1_title.x1() as f64,
|
bail!("no tab bar");
|
||||||
w_mono1_title.y1() as f64,
|
};
|
||||||
);
|
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;
|
client.sync().await;
|
||||||
|
|
||||||
let enters = dss.kb.enter.expect()?;
|
let enters = dss.kb.enter.expect()?;
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -308,9 +308,8 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
let output_damage = connector_data.damage.borrow();
|
let output_damage = connector_data.damage.borrow();
|
||||||
tassert!(!output_damage.is_empty());
|
tassert!(!output_damage.is_empty());
|
||||||
|
|
||||||
// Buffer damage is transformed by the damage matrix which includes the surface position
|
// The test window maps its 1x1 buffer through a viewport to the full window size.
|
||||||
// The buffer damage (0,0,1,1) should be transformed to surface coordinates
|
let expected_buffer_damage = surface_pos;
|
||||||
let expected_buffer_damage = buffer_damage.move_(surface_pos.x1(), surface_pos.y1());
|
|
||||||
|
|
||||||
// Find the exact output damage that matches our expected buffer damage
|
// Find the exact output damage that matches our expected buffer damage
|
||||||
let mut found_exact_buffer_damage = false;
|
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)
|
// Test 7: Check output damage from existing window's viewport (which already has scaling)
|
||||||
connector_data.damage.borrow_mut().clear();
|
connector_data.damage.borrow_mut().clear();
|
||||||
|
|
||||||
// The existing window was created with create_surface_ext() which automatically creates a viewport
|
// 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
|
// Commit the viewport size change separately; that commit intentionally damages the old/new extents.
|
||||||
// First, let's modify the viewport scaling that already exists on the window
|
window.surface.viewport.set_destination(150, 100)?;
|
||||||
window.surface.viewport.set_destination(150, 100)?; // Change scaling to 150x100
|
window.surface.commit()?;
|
||||||
|
client.sync().await;
|
||||||
|
connector_data.damage.borrow_mut().clear();
|
||||||
|
|
||||||
// Add buffer damage to test viewport scaling coordinate transformation
|
// Add buffer damage to test viewport scaling coordinate transformation
|
||||||
window.surface.damage_buffer(0, 0, 1, 1)?; // Damage entire 1x1 buffer
|
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();
|
let output_damage = connector_data.damage.borrow();
|
||||||
tassert!(!output_damage.is_empty());
|
tassert!(!output_damage.is_empty());
|
||||||
|
|
||||||
// With viewporter scaling, the 1x1 buffer damage should scale to 150x100
|
// With viewporter scaling, the 1x1 buffer damage should scale to the viewport destination.
|
||||||
// and be moved by surface position (0, 36) to get output coordinates (0, 36, 150, 136)
|
let surface_pos = window.surface.server.buffer_abs_pos.get();
|
||||||
let expected_scaled_damage = Rect::new_sized(0, 0, 150, 100).unwrap();
|
let expected_scaled_damage = Rect::new_sized(0, 0, 150, 100).unwrap();
|
||||||
let expected_output_damage =
|
let expected_output_damage =
|
||||||
expected_scaled_damage.move_(surface_pos.x1(), surface_pos.y1());
|
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?;
|
rotation_window.map().await?;
|
||||||
client.sync().await;
|
client.sync().await;
|
||||||
|
|
||||||
// Disable viewporter by setting destination to 0x0 to rely purely on buffer dimensions
|
// Disable viewporter to rely purely on buffer dimensions.
|
||||||
rotation_window.surface.viewport.set_destination(0, 0)?; // Disable viewporter
|
rotation_window.surface.viewport.unset_source()?;
|
||||||
|
rotation_window.surface.viewport.unset_destination()?;
|
||||||
|
|
||||||
// Use a rectangular buffer (4x2) so rotation has a visible geometric effect
|
// 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
|
// Attach AFTER mapping to avoid being overwritten by map()'s single-pixel buffer
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ use {
|
||||||
numcell::NumCell,
|
numcell::NumCell,
|
||||||
on_drop_event::OnDropEvent,
|
on_drop_event::OnDropEvent,
|
||||||
rc_eq::rc_eq,
|
rc_eq::rc_eq,
|
||||||
|
scroller::Scroller,
|
||||||
threshold_counter::ThresholdCounter,
|
threshold_counter::ThresholdCounter,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -150,6 +151,7 @@ pub struct ContainerNode {
|
||||||
pub child_removed: Rc<LazyEventSource>,
|
pub child_removed: Rc<LazyEventSource>,
|
||||||
pub all_children_resized: Rc<LazyEventSource>,
|
pub all_children_resized: Rc<LazyEventSource>,
|
||||||
pub tab_bar: RefCell<Option<TabBar>>,
|
pub tab_bar: RefCell<Option<TabBar>>,
|
||||||
|
scroll: Scroller,
|
||||||
pub update_tab_textures_scheduled: Cell<bool>,
|
pub update_tab_textures_scheduled: Cell<bool>,
|
||||||
pub ephemeral: Cell<Ephemeral>,
|
pub ephemeral: Cell<Ephemeral>,
|
||||||
}
|
}
|
||||||
|
|
@ -266,6 +268,7 @@ impl ContainerNode {
|
||||||
child_removed: state.lazy_event_sources.create_source(),
|
child_removed: state.lazy_event_sources.create_source(),
|
||||||
all_children_resized: state.post_layout_event_sources.create_source(),
|
all_children_resized: state.post_layout_event_sources.create_source(),
|
||||||
tab_bar: RefCell::new(None),
|
tab_bar: RefCell::new(None),
|
||||||
|
scroll: Default::default(),
|
||||||
update_tab_textures_scheduled: Cell::new(false),
|
update_tab_textures_scheduled: Cell::new(false),
|
||||||
ephemeral: Cell::new(Ephemeral::Off),
|
ephemeral: Cell::new(Ephemeral::Off),
|
||||||
});
|
});
|
||||||
|
|
@ -793,6 +796,18 @@ impl ContainerNode {
|
||||||
self.activate_child2(child, false);
|
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) {
|
fn activate_child2(self: &Rc<Self>, child: &NodeRef<ContainerChild>, preserve_focus: bool) {
|
||||||
if let Some(mc) = self.mono_child.get() {
|
if let Some(mc) = self.mono_child.get() {
|
||||||
if mc.node.node_id() == child.node.node_id() {
|
if mc.node.node_id() == child.node.node_id() {
|
||||||
|
|
@ -1519,7 +1534,7 @@ impl ContainerNode {
|
||||||
fn button(
|
fn button(
|
||||||
self: Rc<Self>,
|
self: Rc<Self>,
|
||||||
id: CursorType,
|
id: CursorType,
|
||||||
_seat: &Rc<WlSeatGlobal>,
|
seat: &Rc<WlSeatGlobal>,
|
||||||
_time_usec: u64,
|
_time_usec: u64,
|
||||||
pressed: bool,
|
pressed: bool,
|
||||||
button: u32,
|
button: u32,
|
||||||
|
|
@ -1549,7 +1564,7 @@ impl ContainerNode {
|
||||||
if let Some(child) = children.get(&child_id) {
|
if let Some(child) = children.get(&child_id) {
|
||||||
let child_ref = child.to_ref();
|
let child_ref = child.to_ref();
|
||||||
drop(children);
|
drop(children);
|
||||||
self.activate_child(&child_ref);
|
self.activate_child_from_input(&child_ref, seat);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -2066,31 +2081,33 @@ impl Node for ContainerNode {
|
||||||
self.button(id, seat, time_usec, state == ButtonState::Pressed, button);
|
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() {
|
if self.mono_child.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Use vertical scroll (index 1) to switch tabs.
|
let steps = match self.scroll.handle(event) {
|
||||||
let v = match event.v120[1].get() {
|
Some(steps) => steps,
|
||||||
Some(v) if v != 0 => v,
|
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let mono = match self.mono_child.get() {
|
let mut target = match self.mono_child.get() {
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
let next = if v > 0 {
|
let current_id = target.node.node_id();
|
||||||
// Scroll down → next tab.
|
for _ in 0..steps.abs() {
|
||||||
mono.next().or_else(|| self.children.first())
|
let next = if steps > 0 {
|
||||||
} else {
|
target.next().or_else(|| self.children.first())
|
||||||
// Scroll up → previous tab.
|
} else {
|
||||||
mono.prev().or_else(|| self.children.last())
|
target.prev().or_else(|| self.children.last())
|
||||||
};
|
};
|
||||||
if let Some(next) = next {
|
match next {
|
||||||
if next.node.node_id() != mono.node.node_id() {
|
Some(next) => target = next,
|
||||||
self.activate_child(&next);
|
None => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if target.node.node_id() != current_id {
|
||||||
|
self.activate_child_from_input(&target, seat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_on_leave(&self, seat: &WlSeatGlobal) {
|
fn node_on_leave(&self, seat: &WlSeatGlobal) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue