Compare commits
2 commits
feat/scree
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 5db14936e7 | |||
| f777b4c521 |
32 changed files with 181 additions and 77 deletions
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
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 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());
|
||||
|
|
|
|||
|
|
@ -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()?;
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -2,7 +2,7 @@ use {
|
|||
crate::{
|
||||
ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED,
|
||||
it::{
|
||||
test_error::TestResult,
|
||||
test_error::{TestErrorExt, TestResult},
|
||||
test_utils::{
|
||||
test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt,
|
||||
},
|
||||
|
|
@ -10,7 +10,7 @@ use {
|
|||
},
|
||||
},
|
||||
isnt::std_1::collections::IsntHashSetExt,
|
||||
std::rc::Rc,
|
||||
std::{rc::Rc, time::Duration},
|
||||
};
|
||||
|
||||
testcase!();
|
||||
|
|
@ -19,6 +19,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
|||
let ds = run.create_default_setup().await?;
|
||||
|
||||
let client = run.create_client().await?;
|
||||
let default_seat = client.get_default_seat().await?;
|
||||
|
||||
let win1 = client.create_window().await?;
|
||||
win1.set_color(255, 0, 0, 255);
|
||||
|
|
@ -44,5 +45,23 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
|||
client.sync().await;
|
||||
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(())
|
||||
}
|
||||
|
|
|
|||
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();
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -8,18 +8,25 @@ use {
|
|||
renderer::Renderer,
|
||||
state::State,
|
||||
tree::{
|
||||
FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation,
|
||||
OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination,
|
||||
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink,
|
||||
NodeLocation, OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination,
|
||||
WorkspaceNodeId, walker::NodeVisitor,
|
||||
},
|
||||
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 id: NodeId,
|
||||
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 stacked: Rc<LinkedList<Rc<dyn StackedNode>>>,
|
||||
pub stacked_above_layers: Rc<LinkedList<Rc<dyn StackedNode>>>,
|
||||
|
|
@ -31,6 +38,8 @@ impl DisplayNode {
|
|||
let slf = Self {
|
||||
id,
|
||||
extents: Default::default(),
|
||||
visible: Default::default(),
|
||||
suspend_restore_kb_foci: Default::default(),
|
||||
outputs: Default::default(),
|
||||
stacked: Default::default(),
|
||||
stacked_above_layers: Default::default(),
|
||||
|
|
@ -71,6 +80,17 @@ impl DisplayNode {
|
|||
|
||||
pub fn update_visible(&self, state: &State) {
|
||||
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() {
|
||||
output.update_visible();
|
||||
}
|
||||
|
|
@ -82,6 +102,20 @@ impl DisplayNode {
|
|||
for seat in state.globals.seats.lock().values() {
|
||||
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 {
|
||||
state.damage(self.extents.get());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue