1
0
Fork 0
forked from wry/wry

Compare commits

...
Sign in to create a new pull request.

2 commits

32 changed files with 181 additions and 77 deletions

View file

@ -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);
} }
} }
}; };

View file

@ -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 {

View file

@ -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(())
} }

View file

@ -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(())

View file

@ -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());

View file

@ -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()?;

View file

@ -2,7 +2,7 @@ use {
crate::{ crate::{
ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED, ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED,
it::{ it::{
test_error::TestResult, test_error::{TestErrorExt, TestResult},
test_utils::{ test_utils::{
test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt, test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt,
}, },
@ -10,7 +10,7 @@ use {
}, },
}, },
isnt::std_1::collections::IsntHashSetExt, isnt::std_1::collections::IsntHashSetExt,
std::rc::Rc, std::{rc::Rc, time::Duration},
}; };
testcase!(); testcase!();
@ -19,6 +19,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let ds = run.create_default_setup().await?; let ds = run.create_default_setup().await?;
let client = run.create_client().await?; let client = run.create_client().await?;
let default_seat = client.get_default_seat().await?;
let win1 = client.create_window().await?; let win1 = client.create_window().await?;
win1.set_color(255, 0, 0, 255); win1.set_color(255, 0, 0, 255);
@ -44,5 +45,23 @@ async fn test(run: Rc<TestRun>) -> TestResult {
client.sync().await; client.sync().await;
tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED)); tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED));
let leaves = default_seat.kb.leave.expect()?;
let enters = default_seat.kb.enter.expect()?;
run.cfg.set_idle(Duration::from_micros(100))?;
run.cfg.set_idle_grace_period(Duration::from_secs(0))?;
run.state.wheel.timeout(3).await?;
client.sync().await;
tassert!(win2.tl.core.states.borrow().contains(&STATE_SUSPENDED));
let leave = leaves.next().with_context(|| "no leave on suspend")?;
tassert_eq!(leave.surface, win2.surface.id);
ds.mouse.rel(1.0, 1.0);
client.sync().await;
tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED));
let enter = enters.next().with_context(|| "no enter on restore")?;
tassert_eq!(enter.surface, win2.surface.id);
Ok(()) Ok(())
} }

View file

@ -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

View file

@ -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) {

View file

@ -8,18 +8,25 @@ use {
renderer::Renderer, renderer::Renderer,
state::State, state::State,
tree::{ tree::{
FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink,
OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination, NodeLocation, OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination,
WorkspaceNodeId, walker::NodeVisitor, WorkspaceNodeId, walker::NodeVisitor,
}, },
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList}, utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
}, },
std::{cell::Cell, ops::Deref, rc::Rc}, std::{
cell::{Cell, RefCell},
mem,
ops::Deref,
rc::{Rc, Weak},
},
}; };
pub struct DisplayNode { pub struct DisplayNode {
pub id: NodeId, pub id: NodeId,
pub extents: Cell<Rect>, pub extents: Cell<Rect>,
visible: Cell<bool>,
suspend_restore_kb_foci: RefCell<Vec<(Rc<WlSeatGlobal>, Weak<dyn Node>)>>,
pub outputs: CopyHashMap<ConnectorId, Rc<OutputNode>>, pub outputs: CopyHashMap<ConnectorId, Rc<OutputNode>>,
pub stacked: Rc<LinkedList<Rc<dyn StackedNode>>>, pub stacked: Rc<LinkedList<Rc<dyn StackedNode>>>,
pub stacked_above_layers: Rc<LinkedList<Rc<dyn StackedNode>>>, pub stacked_above_layers: Rc<LinkedList<Rc<dyn StackedNode>>>,
@ -31,6 +38,8 @@ impl DisplayNode {
let slf = Self { let slf = Self {
id, id,
extents: Default::default(), extents: Default::default(),
visible: Default::default(),
suspend_restore_kb_foci: Default::default(),
outputs: Default::default(), outputs: Default::default(),
stacked: Default::default(), stacked: Default::default(),
stacked_above_layers: Default::default(), stacked_above_layers: Default::default(),
@ -71,6 +80,17 @@ impl DisplayNode {
pub fn update_visible(&self, state: &State) { pub fn update_visible(&self, state: &State) {
let visible = state.root_visible(); let visible = state.root_visible();
let was_visible = self.visible.replace(visible);
if !visible && was_visible {
let mut foci = self.suspend_restore_kb_foci.borrow_mut();
foci.clear();
for seat in state.globals.seats.lock().values() {
let node = seat.get_keyboard_node();
if node.node_id() != self.id {
foci.push((seat.clone(), Rc::downgrade(&node)));
}
}
}
for output in self.outputs.lock().values() { for output in self.outputs.lock().values() {
output.update_visible(); output.update_visible();
} }
@ -82,6 +102,20 @@ impl DisplayNode {
for seat in state.globals.seats.lock().values() { for seat in state.globals.seats.lock().values() {
seat.set_visible(visible); seat.set_visible(visible);
} }
if visible && !was_visible {
for (seat, node) in mem::take(&mut *self.suspend_restore_kb_foci.borrow_mut()) {
if seat.get_keyboard_node().node_id() == self.id {
if let Some(node) = node.upgrade()
&& node.node_visible()
{
seat.focus_node(node);
} else {
seat.get_fallback_output()
.take_keyboard_navigation_focus(&seat, Direction::Unspecified);
}
}
}
}
if visible { if visible {
state.damage(self.extents.get()); state.damage(self.extents.get());
} }