mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Split off location setting from setup
This commit is contained in:
parent
43809d4210
commit
ce4352a9fb
3 changed files with 282 additions and 173 deletions
|
@ -199,6 +199,9 @@ impl Output {
|
|||
setup.apply(output, &tag_mod);
|
||||
}
|
||||
}
|
||||
if let Some(tag) = output.tags().first() {
|
||||
tag.set_active(true);
|
||||
}
|
||||
};
|
||||
|
||||
let outputs = self.get_all();
|
||||
|
@ -211,10 +214,18 @@ impl Output {
|
|||
})));
|
||||
}
|
||||
|
||||
pub fn setup_locs(&self, setup: OutputLocSetup) {
|
||||
pub fn setup_locs(
|
||||
&self,
|
||||
update_locs_on: UpdateLocsOn,
|
||||
setup: impl IntoIterator<Item = (impl ToString, OutputLoc)>,
|
||||
) {
|
||||
let setup: HashMap<_, _> = setup
|
||||
.into_iter()
|
||||
.map(|(name, align)| (name.to_string(), align))
|
||||
.collect();
|
||||
|
||||
let api = self.api.get().unwrap().clone();
|
||||
let layout_outputs = move || {
|
||||
let setups = setups_clone.clone().into_iter().collect::<Vec<_>>();
|
||||
let outputs = api.output.get_all();
|
||||
|
||||
let mut rightmost_output_and_x: Option<(OutputHandle, i32)> = None;
|
||||
|
@ -225,9 +236,7 @@ impl Output {
|
|||
|
||||
// Place outputs with OutputSetupLoc::Point
|
||||
for output in outputs.iter() {
|
||||
for setup in setups.iter() {
|
||||
if setup.output.matches(output) {
|
||||
if let Some(OutputLoc::Point(x, y)) = setup.loc {
|
||||
if let Some(&OutputLoc::Point(x, y)) = setup.get(output.name()) {
|
||||
output.set_location(x, y);
|
||||
|
||||
placed_outputs.insert(output.clone());
|
||||
|
@ -243,8 +252,6 @@ impl Output {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Place everything without an explicit location to the right of the rightmost output
|
||||
for output in outputs
|
||||
|
@ -252,13 +259,11 @@ impl Output {
|
|||
.filter(|op| !placed_outputs.contains(op))
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
for setup in setups.iter() {
|
||||
if setup.output.matches(output) && setup.loc.is_none() {
|
||||
if setup.get(output.name()).is_none() {
|
||||
if let Some((rm_op, _)) = rightmost_output_and_x.as_ref() {
|
||||
output.set_loc_adj_to(rm_op, Alignment::RightAlignTop);
|
||||
} else {
|
||||
output.set_location(0, 0);
|
||||
println!("SET LOC FOR {} TO (0, 0)", output.name());
|
||||
}
|
||||
|
||||
placed_outputs.insert(output.clone());
|
||||
|
@ -274,26 +279,28 @@ impl Output {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to place relative outputs
|
||||
while let Some((output, relative_to, alignment)) = setups.iter().find_map(|setup| {
|
||||
outputs.iter().find_map(|op| {
|
||||
if !placed_outputs.contains(op) && setup.output.matches(op) {
|
||||
match &setup.loc {
|
||||
Some(OutputLoc::RelativeTo(matcher, alignment)) => {
|
||||
let first_matched_op = outputs
|
||||
while let Some((output, relative_to, alignment)) =
|
||||
setup.iter().find_map(|(setup_op_name, loc)| {
|
||||
outputs
|
||||
.iter()
|
||||
.find(|o| matcher.matches(o) && placed_outputs.contains(o))?;
|
||||
Some((op, first_matched_op, alignment))
|
||||
.find(|setup_op| {
|
||||
!placed_outputs.contains(setup_op) && setup_op.name() == setup_op_name
|
||||
})
|
||||
.and_then(|setup_op| match loc {
|
||||
OutputLoc::RelativeTo(relative_tos) => {
|
||||
relative_tos.iter().find_map(|(rel_name, align)| {
|
||||
placed_outputs.iter().find_map(|pl_op| {
|
||||
(pl_op.name() == rel_name)
|
||||
.then_some((setup_op, pl_op, align))
|
||||
})
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}) {
|
||||
})
|
||||
{
|
||||
output.set_loc_adj_to(relative_to, *alignment);
|
||||
|
||||
placed_outputs.insert(output.clone());
|
||||
|
@ -349,19 +356,25 @@ impl Output {
|
|||
let layout_outputs_clone1 = layout_outputs.clone();
|
||||
let layout_outputs_clone2 = layout_outputs.clone();
|
||||
|
||||
self.connect_signal(OutputSignal::Connect(Box::new(move |output| {
|
||||
if update_locs_on.contains(UpdateLocsOn::CONNECT) {
|
||||
self.connect_signal(OutputSignal::Connect(Box::new(move |_| {
|
||||
layout_outputs_clone2();
|
||||
})));
|
||||
}
|
||||
|
||||
if update_locs_on.contains(UpdateLocsOn::DISCONNECT) {
|
||||
self.connect_signal(OutputSignal::Disconnect(Box::new(move |_| {
|
||||
layout_outputs_clone1();
|
||||
})));
|
||||
}
|
||||
|
||||
if update_locs_on.contains(UpdateLocsOn::RESIZE) {
|
||||
self.connect_signal(OutputSignal::Resize(Box::new(move |_, _, _| {
|
||||
layout_outputs();
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A matcher for outputs.
|
||||
pub enum OutputMatcher {
|
||||
|
@ -481,45 +494,36 @@ impl OutputSetup {
|
|||
output.set_scale(scale);
|
||||
}
|
||||
if let Some(tag_names) = &self.tag_names {
|
||||
let tags = tag.add(output, tag_names);
|
||||
if let Some(tag) = tags.first() {
|
||||
tag.set_active(true);
|
||||
}
|
||||
tag.add(output, tag_names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A location for an output.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum OutputLoc {
|
||||
/// A specific point in the global space.
|
||||
Point(i32, i32),
|
||||
/// A location relative to another output.
|
||||
///
|
||||
/// This holds a `Vec` of output names to alignments.
|
||||
/// The output that is relative to will be chosen from the first
|
||||
/// connected and placed output in this `Vec`.
|
||||
RelativeTo(Vec<(String, Alignment)>),
|
||||
}
|
||||
|
||||
pub struct OutputLocSetup {
|
||||
update_locs_on: UpdateLocsOn,
|
||||
setup: HashMap<String, OutputLoc>,
|
||||
}
|
||||
|
||||
impl OutputLocSetup {
|
||||
pub fn new(
|
||||
update_locs_on: UpdateLocsOn,
|
||||
setup: impl IntoIterator<Item = (impl ToString, OutputLoc)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
update_locs_on,
|
||||
setup: setup
|
||||
.into_iter()
|
||||
.map(|(s, loc)| (s.to_string(), loc))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputLoc {
|
||||
pub fn from_point(x: i32, y: i32) -> Self {
|
||||
Self::Point(x, y)
|
||||
/// Creates an `OutputLoc` that will place outputs relative to
|
||||
/// the output with the given name using the given alignment.
|
||||
pub fn relative_to(name: impl ToString, alignment: Alignment) -> Self {
|
||||
Self::RelativeTo(vec![(name.to_string(), alignment)])
|
||||
}
|
||||
|
||||
pub fn from_relatives(relatives: impl IntoIterator<Item = (impl ToString, Alignment)>) -> Self {
|
||||
/// Creates an `OutputLoc` from multiple (output_name, alignment) pairs
|
||||
/// that serve as fallbacks.
|
||||
pub fn relative_to_with_fallbacks(
|
||||
relatives: impl IntoIterator<Item = (impl ToString, Alignment)>,
|
||||
) -> Self {
|
||||
Self::RelativeTo(
|
||||
relatives
|
||||
.into_iter()
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use std::{panic::UnwindSafe, time::Duration};
|
||||
|
||||
use pinnacle::{backend::dummy::setup_dummy, state::State};
|
||||
use smithay::reexports::calloop::{
|
||||
use smithay::{
|
||||
output::Output,
|
||||
reexports::calloop::{
|
||||
self,
|
||||
channel::{Event, Sender},
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
|
@ -74,3 +77,12 @@ pub fn test_api(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn output_for_name(state: &State, name: &str) -> Output {
|
||||
state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == name)
|
||||
.unwrap()
|
||||
.clone()
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::thread::JoinHandle;
|
|||
use pinnacle_api::ApiModules;
|
||||
use test_log::test;
|
||||
|
||||
use crate::common::output_for_name;
|
||||
use crate::common::{sleep_secs, test_api, with_state};
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -23,42 +24,27 @@ fn setup_rust(run: impl FnOnce(ApiModules) + Send + 'static) -> JoinHandle<()> {
|
|||
}
|
||||
|
||||
mod output {
|
||||
use pinnacle_api::output::{Alignment, OutputMatcher, OutputSetup};
|
||||
use smithay::utils::Rectangle;
|
||||
use pinnacle::state::WithState;
|
||||
use pinnacle_api::output::{Alignment, OutputLoc, OutputSetup, UpdateLocsOn};
|
||||
use smithay::{output::Output, utils::Rectangle};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn setup() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
setup_rust(|api| {
|
||||
api.output
|
||||
.setup([OutputSetup::new_with_matcher(|_| true).with_tags(["1", "2", "3"])]);
|
||||
});
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
state.new_output("First", (300, 200).into());
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn setup_with_cyclic_relative_locs_work() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
setup_rust(|api| {
|
||||
api.output.setup([
|
||||
OutputSetup::new("Pinnacle Window"),
|
||||
OutputSetup::new("First").with_relative_loc(
|
||||
OutputMatcher::Name("Second".into()),
|
||||
Alignment::RightAlignTop,
|
||||
),
|
||||
OutputSetup::new("Second").with_relative_loc(
|
||||
OutputMatcher::Name("First".into()),
|
||||
Alignment::LeftAlignTop,
|
||||
OutputSetup::new_with_matcher(|_| true).with_tags(["1", "2", "3"]),
|
||||
OutputSetup::new_with_matcher(|op| op.name().contains("Test"))
|
||||
.with_tags(["Test 4", "Test 5"]),
|
||||
OutputSetup::new("Second").with_scale(2.0).with_mode(
|
||||
pinnacle_api::output::Mode {
|
||||
pixel_width: 6900,
|
||||
pixel_height: 420,
|
||||
refresh_rate_millihertz: 69420,
|
||||
},
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
@ -67,24 +53,91 @@ mod output {
|
|||
|
||||
with_state(&sender, |state| {
|
||||
state.new_output("First", (300, 200).into());
|
||||
state.new_output("Second", (300, 200).into());
|
||||
state.new_output("Test Third", (300, 200).into());
|
||||
});
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
let original_op = state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == "Pinnacle Window")
|
||||
.unwrap();
|
||||
let first_op = state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == "First")
|
||||
.unwrap();
|
||||
let original_op = output_for_name(state, "Pinnacle Window");
|
||||
let first_op = output_for_name(state, "First");
|
||||
let second_op = output_for_name(state, "Second");
|
||||
let test_third_op = output_for_name(state, "Test Third");
|
||||
|
||||
let original_geo = state.space.output_geometry(original_op).unwrap();
|
||||
let first_geo = state.space.output_geometry(first_op).unwrap();
|
||||
let tags_for = |output: &Output| {
|
||||
output
|
||||
.with_state(|state| state.tags.iter().map(|t| t.name()).collect::<Vec<_>>())
|
||||
};
|
||||
|
||||
let focused_tags_for = |output: &Output| {
|
||||
output.with_state(|state| {
|
||||
state.focused_tags().map(|t| t.name()).collect::<Vec<_>>()
|
||||
})
|
||||
};
|
||||
|
||||
assert_eq!(tags_for(&original_op), vec!["1", "2", "3"]);
|
||||
assert_eq!(tags_for(&first_op), vec!["1", "2", "3"]);
|
||||
assert_eq!(tags_for(&second_op), vec!["1", "2", "3"]);
|
||||
assert_eq!(
|
||||
tags_for(&test_third_op),
|
||||
vec!["1", "2", "3", "Test 4", "Test 5"]
|
||||
);
|
||||
|
||||
assert_eq!(focused_tags_for(&original_op), vec!["1"]);
|
||||
assert_eq!(focused_tags_for(&test_third_op), vec!["1"]);
|
||||
|
||||
assert_eq!(second_op.current_scale().fractional_scale(), 2.0);
|
||||
|
||||
let second_mode = second_op.current_mode().unwrap();
|
||||
assert_eq!(second_mode.size.w, 6900);
|
||||
assert_eq!(second_mode.size.h, 420);
|
||||
assert_eq!(second_mode.refresh, 69420);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn setup_loc_with_cyclic_relative_locs_work() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
setup_rust(|api| {
|
||||
api.output.setup_locs(
|
||||
UpdateLocsOn::all(),
|
||||
[
|
||||
("Pinnacle Window", OutputLoc::Point(0, 0)),
|
||||
(
|
||||
"First",
|
||||
OutputLoc::relative_to_with_fallbacks([(
|
||||
"Second",
|
||||
Alignment::LeftAlignTop,
|
||||
)]),
|
||||
),
|
||||
(
|
||||
"Second",
|
||||
OutputLoc::relative_to_with_fallbacks([(
|
||||
"First",
|
||||
Alignment::RightAlignTop,
|
||||
)]),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
state.new_output("First", (300, 200).into());
|
||||
});
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
let original_op = output_for_name(state, "Pinnacle Window");
|
||||
let first_op = output_for_name(state, "First");
|
||||
|
||||
let original_geo = state.space.output_geometry(&original_op).unwrap();
|
||||
let first_geo = state.space.output_geometry(&first_op).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
original_geo,
|
||||
|
@ -101,25 +154,13 @@ mod output {
|
|||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
let original_op = state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == "Pinnacle Window")
|
||||
.unwrap();
|
||||
let first_op = state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == "First")
|
||||
.unwrap();
|
||||
let second_op = state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == "Second")
|
||||
.unwrap();
|
||||
let original_op = output_for_name(state, "Pinnacle Window");
|
||||
let first_op = output_for_name(state, "First");
|
||||
let second_op = output_for_name(state, "Second");
|
||||
|
||||
let original_geo = state.space.output_geometry(original_op).unwrap();
|
||||
let first_geo = state.space.output_geometry(first_op).unwrap();
|
||||
let second_geo = state.space.output_geometry(second_op).unwrap();
|
||||
let original_geo = state.space.output_geometry(&original_op).unwrap();
|
||||
let first_geo = state.space.output_geometry(&first_op).unwrap();
|
||||
let second_geo = state.space.output_geometry(&second_op).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
original_geo,
|
||||
|
@ -139,45 +180,97 @@ mod output {
|
|||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn setup_with_relative_output_matcher_works() -> anyhow::Result<()> {
|
||||
async fn setup_loc_with_relative_locs_with_more_than_one_relative_works() -> anyhow::Result<()>
|
||||
{
|
||||
test_api(|sender| {
|
||||
setup_rust(|api| {
|
||||
api.output.setup([
|
||||
OutputSetup::new("Pinnacle Window"),
|
||||
OutputSetup::new_with_matcher(|_| true)
|
||||
.with_relative_loc(|_: &_| true, Alignment::BottomAlignLeft),
|
||||
]);
|
||||
api.output.setup_locs(
|
||||
UpdateLocsOn::all(),
|
||||
[
|
||||
("Pinnacle Window", OutputLoc::Point(0, 0)),
|
||||
(
|
||||
"First",
|
||||
OutputLoc::relative_to("Pinnacle Window", Alignment::BottomAlignLeft),
|
||||
),
|
||||
(
|
||||
"Second",
|
||||
OutputLoc::relative_to("First", Alignment::BottomAlignLeft),
|
||||
),
|
||||
(
|
||||
"Third",
|
||||
OutputLoc::relative_to_with_fallbacks([
|
||||
("Second", Alignment::BottomAlignLeft),
|
||||
("First", Alignment::BottomAlignLeft),
|
||||
]),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
state.new_output("First", (300, 200).into());
|
||||
state.new_output("Second", (400, 600).into());
|
||||
state.new_output("Third", (400, 300).into());
|
||||
state.new_output("Second", (300, 700).into());
|
||||
state.new_output("Third", (300, 400).into());
|
||||
});
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
for output in state.space.outputs() {
|
||||
let geo = state.space.output_geometry(output).unwrap();
|
||||
match output.name().as_str() {
|
||||
"Pinnacle Window" => {
|
||||
assert_eq!(geo, Rectangle::from_loc_and_size((0, 0), (1920, 1080)));
|
||||
}
|
||||
"First" => {
|
||||
assert_eq!(geo, Rectangle::from_loc_and_size((0, 1080), (300, 200)));
|
||||
}
|
||||
"Second" => {
|
||||
assert_eq!(geo, Rectangle::from_loc_and_size((0, 1080), (400, 600)));
|
||||
}
|
||||
"Third" => {
|
||||
assert_eq!(geo, Rectangle::from_loc_and_size((0, 1080), (400, 300)));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
let original_op = output_for_name(state, "Pinnacle Window");
|
||||
let first_op = output_for_name(state, "First");
|
||||
let second_op = output_for_name(state, "Second");
|
||||
let third_op = output_for_name(state, "Third");
|
||||
|
||||
let original_geo = state.space.output_geometry(&original_op).unwrap();
|
||||
let first_geo = state.space.output_geometry(&first_op).unwrap();
|
||||
let second_geo = state.space.output_geometry(&second_op).unwrap();
|
||||
let third_geo = state.space.output_geometry(&third_op).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
original_geo,
|
||||
Rectangle::from_loc_and_size((0, 0), (1920, 1080))
|
||||
);
|
||||
assert_eq!(
|
||||
first_geo,
|
||||
Rectangle::from_loc_and_size((0, 1080), (300, 200))
|
||||
);
|
||||
assert_eq!(
|
||||
second_geo,
|
||||
Rectangle::from_loc_and_size((0, 1080 + 200), (300, 700))
|
||||
);
|
||||
assert_eq!(
|
||||
third_geo,
|
||||
Rectangle::from_loc_and_size((0, 1080 + 200 + 700), (300, 400))
|
||||
);
|
||||
|
||||
state.remove_output(&second_op);
|
||||
});
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
let original_op = output_for_name(state, "Pinnacle Window");
|
||||
let first_op = output_for_name(state, "First");
|
||||
let third_op = output_for_name(state, "Third");
|
||||
|
||||
let original_geo = state.space.output_geometry(&original_op).unwrap();
|
||||
let first_geo = state.space.output_geometry(&first_op).unwrap();
|
||||
let third_geo = state.space.output_geometry(&third_op).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
original_geo,
|
||||
Rectangle::from_loc_and_size((0, 0), (1920, 1080))
|
||||
);
|
||||
assert_eq!(
|
||||
first_geo,
|
||||
Rectangle::from_loc_and_size((0, 1080), (300, 200))
|
||||
);
|
||||
assert_eq!(
|
||||
third_geo,
|
||||
Rectangle::from_loc_and_size((0, 1080 + 200), (300, 400))
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue