tests: Make panel manager modifications pure

This makes testing possible. Test which prompted this is included.
This commit is contained in:
Dorota Czaplejewicz
2023-03-15 15:10:38 +00:00
parent b17716a427
commit 83fda9d38a
2 changed files with 175 additions and 65 deletions

View File

@ -29,9 +29,14 @@ pub mod c {
pub struct WlOutput(*const c_void); pub struct WlOutput(*const c_void);
impl WlOutput { impl WlOutput {
fn null() -> Self { const fn null() -> Self {
Self(ptr::null()) Self(ptr::null())
} }
#[cfg(test)]
pub const fn dummy() -> Self {
Self::null()
}
} }
#[repr(C)] #[repr(C)]

View File

@ -94,7 +94,7 @@ impl PixelSize {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
struct Size { struct Size {
width: u32, width: u32,
height: u32, height: u32,
@ -104,7 +104,7 @@ struct Size {
/// the application asks for some size, /// the application asks for some size,
/// and then receives a size that the compositor thought appropriate. /// and then receives a size that the compositor thought appropriate.
/// Stores raw values passed to Wayland, i.e. scaled dimensions. /// Stores raw values passed to Wayland, i.e. scaled dimensions.
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
enum State { enum State {
Hidden, Hidden,
SizeRequested { SizeRequested {
@ -119,6 +119,18 @@ enum State {
}, },
} }
/// A command to send out to the next layer of processing.
/// Here, it's the C side of the panel.
#[derive(Debug, PartialEq)]
enum Update {
Hide,
Resize { height: u32 },
RequestWidget {
output: OutputId,
height: u32,
},
}
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum Command { pub enum Command {
Show { Show {
@ -153,7 +165,50 @@ impl Manager {
eprintln!("Panel received configure {:?}", &size); eprintln!("Panel received configure {:?}", &size);
} }
self.state = match self.state.clone() { self.state = self.state.clone().configure(size);
if self.debug {
eprintln!("Panel now {:?}", &self.state);
}
}
pub fn update(mgr: Wrapped<Manager>, cmd: Command) {
let copied = mgr.clone();
let mgr = mgr.clone_ref();
let mut mgr = mgr.borrow_mut();
if mgr.debug {
eprintln!("Panel received {:?}", &cmd);
}
let (state, updates) = mgr.state.clone().update(cmd);
(*mgr).state = state;
for update in &updates {
unsafe {
match update {
Update::Hide => c::panel_manager_hide(mgr.panel),
Update::Resize { height }
=> c::panel_manager_resize(mgr.panel, *height),
Update::RequestWidget{output, height}
=> c::panel_manager_request_widget(mgr.panel, output.0, *height, copied.clone()),
}
}
}
if mgr.debug {
for update in &updates {
eprintln!("Panel updates: {:?}", &update);
}
eprintln!("Panel is now {:?}", &(*mgr).state);
}
}
}
impl State {
fn configure(self, size: Size) -> Self {
match self {
State::Hidden => { State::Hidden => {
// This may happen if a hide is scheduled immediately after a show. // This may happen if a hide is scheduled immediately after a show.
log_print!( log_print!(
@ -174,49 +229,34 @@ impl Manager {
wanted_height: height, wanted_height: height,
allocated: size, allocated: size,
}, },
};
if self.debug {
eprintln!("Panel now {:?}", &self.state);
} }
} }
pub fn update(mgr: Wrapped<Manager>, cmd: Command) { fn update(self, cmd: Command) -> (Self, Vec<Update>) {
let copied = mgr.clone(); match (cmd, self) {
(Command::Hide, State::Hidden) => (State::Hidden, Vec::new()),
let mgr = mgr.clone_ref(); (Command::Hide, State::SizeAllocated{..}) => (
let mut mgr = mgr.borrow_mut(); State::Hidden, vec![Update::Hide],
),
if mgr.debug { (Command::Hide, State::SizeRequested{..}) => (
eprintln!("Panel received {:?}", &cmd); State::Hidden, vec![Update::Hide],
} ),
(*mgr).state = match (cmd, mgr.state.clone()) {
(Command::Hide, State::Hidden) => State::Hidden,
(Command::Hide, State::SizeAllocated{..}) => {
unsafe { c::panel_manager_hide(mgr.panel); }
State::Hidden
},
(Command::Hide, State::SizeRequested{..}) => {
unsafe { c::panel_manager_hide(mgr.panel); }
State::Hidden
},
(Command::Show{output, height}, State::Hidden) => { (Command::Show{output, height}, State::Hidden) => {
let height = height.as_scaled_ceiling(); let height = height.as_scaled_ceiling();
if mgr.debug { (
eprintln!("Panel requests widget {:?}", (&output.0, &height)); State::SizeRequested{output, height},
} vec![Update::RequestWidget{ output, height }],
unsafe { c::panel_manager_request_widget(mgr.panel, output.0, height, copied); } )
State::SizeRequested{output, height}
}, },
( (
Command::Show{output, height}, Command::Show{output, height},
State::SizeRequested{output: req_output, height: req_height}, State::SizeRequested{output: req_output, height: req_height},
) => { ) => {
let height = height.as_scaled_ceiling(); let height = height.as_scaled_ceiling();
if output == req_output && height == req_height { if output == req_output && height == req_height {(
State::SizeRequested{output: req_output, height: req_height} State::SizeRequested{output: req_output, height: req_height},
} else if output == req_output { Vec::new(),
)} else if output == req_output {
// I'm not sure about that. // I'm not sure about that.
// This could cause a busy loop, // This could cause a busy loop,
// when two requests are being processed at the same time: // when two requests are being processed at the same time:
@ -225,50 +265,115 @@ impl Manager {
// the other from the state wanting height B', // the other from the state wanting height B',
// causing the compositor to change size to B. // causing the compositor to change size to B.
// So better cut this short here, despite artifacts. // So better cut this short here, despite artifacts.
// Out of simplicty, just ignore the new request. // Out of simplicty, just ignore the new request.
// If that causes problems, the request in flight could be stored // If that causes problems, the request in flight could be stored
// for the purpose of handling it better somehow. // for the purpose of handling it better somehow.
State::SizeRequested{output: req_output, height: req_height}
} else { //BUGGY
if mgr.debug { (
eprintln!("Panel requests widget {:?}", (&output.0, &height)); State::SizeRequested{output: req_output, height: req_height},
} Vec::new(),
)
} else {(
// This looks weird, but should be safe. // This looks weird, but should be safe.
// The stack seems to handle // The stack seems to handle
// configure events on a dead surface. // configure events on a dead surface.
unsafe { State::SizeRequested{output, height},
c::panel_manager_hide(mgr.panel); vec![
c::panel_manager_request_widget(mgr.panel, output.0, height, copied); Update::Hide,
} Update::RequestWidget { output, height },
State::SizeRequested{output, height} ],
} )}
}, },
( (
Command::Show{output, height}, Command::Show{output, height},
State::SizeAllocated{output: alloc_output, allocated, wanted_height}, State::SizeAllocated{output: alloc_output, allocated, wanted_height},
) => { ) => {
let height = height.as_scaled_ceiling(); let height = height.as_scaled_ceiling();
if output == alloc_output && height == wanted_height { if output == alloc_output && height == wanted_height {(
State::SizeAllocated{output: alloc_output, wanted_height, allocated} State::SizeAllocated{output: alloc_output, wanted_height, allocated},
} else if output == alloc_output && height == allocated.height { Vec::new(),
State::SizeAllocated{output: alloc_output, wanted_height: height, allocated} )} else if output == alloc_output && height == allocated.height {(
} else if output == alloc_output { State::SizeAllocated{output: alloc_output, wanted_height: height, allocated},
Vec::new(),
)} else if output == alloc_output {(
// Should *all* other heights cause a resize? // Should *all* other heights cause a resize?
// What about those between wanted and allocated? // What about those between wanted and allocated?
unsafe { c::panel_manager_resize(mgr.panel, height); } State::SizeRequested{output, height},
State::SizeRequested{output, height} vec![Update::Resize{height}],
} else { )} else {(
unsafe { State::SizeRequested{output, height},
c::panel_manager_hide(mgr.panel); vec![
c::panel_manager_request_widget(mgr.panel, output.0, height, copied); Update::Hide,
} Update::RequestWidget{output, height},
State::SizeRequested{output, height} ]
} )}
}, },
}; }
}
}
if mgr.debug { #[cfg(test)]
eprintln!("Panel is now {:?}", &(*mgr).state); mod tests {
} use super::*;
use crate::outputs::c::WlOutput;
#[test]
fn resize_before_configured() {
// allow to make typing fields easier
#[allow(non_upper_case_globals)]
const output: OutputId = OutputId(WlOutput::dummy());
let state = State::Hidden;
// Initial show
let (state, cmds) = state.update(Command::Show {
output,
height: PixelSize { pixels: 100, scale_factor: 1 },
});
assert_eq!(
cmds,
vec![Update::RequestWidget { output, height: 100 }],
);
// layer shell requests a resize
// but another show comes before first can be confirmed
let (state, cmds) = dbg!(state).update(Command::Show {
output,
height: PixelSize { pixels: 50, scale_factor: 1 },
});
// what's the expected outcome here? Should the failed request be kept?
assert_eq!(
cmds,
vec![Update::RequestWidget { output, height: 50 }],
"{:?}",
state,
);
// This is too many layers of indirection, but as long as layer shell is tied to gtk widgets, there's not much to be done.
// event we want
let good_state = state.clone().configure(Size { width: 50, height: 50 });
assert_eq!(
good_state,
State::SizeAllocated {
output,
wanted_height: 50,
allocated: Size { width: 50, height: 50 },
},
);
// or event we do not want
let state = state.configure(Size { width: 50, height: 100 });
// followed by the good one
let state = state.configure(Size { width: 50, height: 50 });
assert_eq!(
state,
State::SizeAllocated {
output,
wanted_height: 50,
allocated: Size { width: 50, height: 50 },
},
);
} }
} }