Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,10 @@ pub trait LayoutElement {

#[derive(Debug)]
pub struct Layout<W: LayoutElement> {
/// Monitors and workspaes in the layout.
/// Monitors and workspaces in the layout.
monitor_set: MonitorSet<W>,
/// Focused outputs ordered from least to most recently focused.
output_focus_stack: Vec<Output>,
/// Whether the layout should draw as active.
///
/// This normally indicates that the layout has keyboard focus, but not always. E.g. when the
Expand Down Expand Up @@ -688,13 +690,23 @@ impl OverviewProgress {
}

impl<W: LayoutElement> Layout<W> {
pub fn outputs_by_recent_focus(&self) -> impl DoubleEndedIterator<Item = &Output> {
self.output_focus_stack.iter()
}

fn note_output_focus(&mut self, output: &Output) {
self.output_focus_stack.retain(|focused| focused != output);
self.output_focus_stack.push(output.clone());
}

pub fn new(clock: Clock, config: &Config) -> Self {
Self::with_options_and_workspaces(clock, config, Options::from_config(config))
}

pub fn with_options(clock: Clock, options: Options) -> Self {
Self {
monitor_set: MonitorSet::NoOutputs { workspaces: vec![] },
output_focus_stack: vec![],
is_active: true,
last_active_workspace_id: HashMap::new(),
interactive_move: None,
Expand All @@ -720,6 +732,7 @@ impl<W: LayoutElement> Layout<W> {

Self {
monitor_set: MonitorSet::NoOutputs { workspaces },
output_focus_stack: vec![],
is_active: true,
last_active_workspace_id: HashMap::new(),
interactive_move: None,
Expand Down Expand Up @@ -831,6 +844,9 @@ impl<W: LayoutElement> Layout<W> {
monitor.overview_open = self.overview_open;
monitor.set_overview_progress(self.overview_progress.as_ref());

self.output_focus_stack.clear();
self.output_focus_stack.push(monitor.output.clone());

MonitorSet::Normal {
monitors: vec![monitor],
primary_idx: 0,
Expand All @@ -841,6 +857,7 @@ impl<W: LayoutElement> Layout<W> {
}

pub fn remove_output(&mut self, output: &Output) {
self.output_focus_stack.retain(|focused| focused != output);
self.monitor_set = match mem::take(&mut self.monitor_set) {
MonitorSet::Normal {
mut monitors,
Expand Down Expand Up @@ -918,6 +935,7 @@ impl<W: LayoutElement> Layout<W> {

if activate {
*active_monitor_idx = monitor_idx;
self.note_output_focus(&monitors[monitor_idx].output);
}
}

Expand Down Expand Up @@ -1017,6 +1035,7 @@ impl<W: LayoutElement> Layout<W> {

if activate.map_smart(|| false) {
*active_monitor_idx = mon_idx;
self.note_output_focus(&mon.output);
}

// Set the default height for scrolling windows.
Expand Down Expand Up @@ -1531,6 +1550,7 @@ impl<W: LayoutElement> Layout<W> {
for (workspace_idx, ws) in mon.workspaces.iter_mut().enumerate() {
if ws.activate_window(window) {
*active_monitor_idx = monitor_idx;
self.note_output_focus(&mon.output);

// If currently in the middle of a vertical swipe between the target workspace
// and some other, don't switch the workspace.
Expand Down Expand Up @@ -1567,6 +1587,7 @@ impl<W: LayoutElement> Layout<W> {
for (workspace_idx, ws) in mon.workspaces.iter_mut().enumerate() {
if ws.activate_window_without_raising(window) {
*active_monitor_idx = monitor_idx;
self.note_output_focus(&mon.output);

// If currently in the middle of a vertical swipe between the target workspace
// and some other, don't switch the workspace.
Expand Down Expand Up @@ -3274,6 +3295,7 @@ impl<W: LayoutElement> Layout<W> {
for (idx, mon) in monitors.iter().enumerate() {
if &mon.output == output {
*active_monitor_idx = idx;
self.note_output_focus(output);
return;
}
}
Expand Down Expand Up @@ -3373,6 +3395,7 @@ impl<W: LayoutElement> Layout<W> {
);
if activate.map_smart(|| false) {
*active_monitor_idx = new_idx;
self.note_output_focus(&monitors[new_idx].output);
}

let mon = &mut monitors[mon_idx];
Expand Down Expand Up @@ -3488,6 +3511,7 @@ impl<W: LayoutElement> Layout<W> {

if activate {
*active_monitor_idx = target_idx;
self.note_output_focus(&monitors[target_idx].output);
}

activate
Expand Down
94 changes: 66 additions & 28 deletions src/niri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3462,20 +3462,65 @@ impl Niri {
self.global_space.output_under(pos).next().cloned()
}

fn closest_output_in_direction(
&self,
current: &Output,
extended_geo: Rectangle<i32, Logical>,
metrics: impl Fn(Rectangle<i32, Logical>, Rectangle<i32, Logical>) -> Option<(i32, i32)>,
) -> Option<Output> {
let current_geo = self.global_space.output_geometry(current)?;

let candidates = self
.global_space
.outputs()
.filter(|&output| output != current)
.filter_map(|output| {
let geo = self.global_space.output_geometry(output).unwrap();
let metrics = metrics(current_geo, geo)?;
geo.overlaps(extended_geo).then_some((output, metrics))
})
.collect::<Vec<_>>();

Self::pick_closest_output(self.layout.outputs_by_recent_focus(), candidates)
}

fn pick_closest_output<'a>(
recent_focus: impl DoubleEndedIterator<Item = &'a Output>,
candidates: impl IntoIterator<Item = (&'a Output, (i32, i32))>,
) -> Option<Output> {
let candidates = candidates.into_iter().collect::<Vec<_>>();
let best_edge = candidates.iter().map(|(_, (edge, _))| *edge).max()?;

recent_focus
.rev()
.find(|focused| {
candidates
.iter()
.any(|(candidate, (edge, _))| *edge == best_edge && *candidate == *focused)
})
.cloned()
.or_else(|| {
candidates
.iter()
.filter(|(_, (edge, _))| *edge == best_edge)
.min_by_key(|(_, (_, orthogonal_distance))| *orthogonal_distance)
.map(|(output, _)| (*output).clone())
})
}

pub fn output_left_of(&self, current: &Output) -> Option<Output> {
let current_geo = self.global_space.output_geometry(current)?;
let extended_geo = Rectangle::new(
Point::from((i32::MIN / 2, current_geo.loc.y)),
Size::from((i32::MAX, current_geo.size.h)),
);

self.global_space
.outputs()
.map(|output| (output, self.global_space.output_geometry(output).unwrap()))
.filter(|(_, geo)| center(*geo).x < center(current_geo).x && geo.overlaps(extended_geo))
.min_by_key(|(_, geo)| center(current_geo).x - center(*geo).x)
.map(|(output, _)| output)
.cloned()
self.closest_output_in_direction(current, extended_geo, |current_geo, geo| {
(center(geo).x < center(current_geo).x).then_some((
geo.loc.x + geo.size.w,
(center(geo).y - center(current_geo).y).abs(),
))
})
}

pub fn output_right_of(&self, current: &Output) -> Option<Output> {
Expand All @@ -3485,13 +3530,10 @@ impl Niri {
Size::from((i32::MAX, current_geo.size.h)),
);

self.global_space
.outputs()
.map(|output| (output, self.global_space.output_geometry(output).unwrap()))
.filter(|(_, geo)| center(*geo).x > center(current_geo).x && geo.overlaps(extended_geo))
.min_by_key(|(_, geo)| center(*geo).x - center(current_geo).x)
.map(|(output, _)| output)
.cloned()
self.closest_output_in_direction(current, extended_geo, |current_geo, geo| {
(center(geo).x > center(current_geo).x)
.then_some((-geo.loc.x, (center(geo).y - center(current_geo).y).abs()))
})
}

pub fn output_up_of(&self, current: &Output) -> Option<Output> {
Expand All @@ -3501,13 +3543,12 @@ impl Niri {
Size::from((current_geo.size.w, i32::MAX)),
);

self.global_space
.outputs()
.map(|output| (output, self.global_space.output_geometry(output).unwrap()))
.filter(|(_, geo)| center(*geo).y < center(current_geo).y && geo.overlaps(extended_geo))
.min_by_key(|(_, geo)| center(current_geo).y - center(*geo).y)
.map(|(output, _)| output)
.cloned()
self.closest_output_in_direction(current, extended_geo, |current_geo, geo| {
(center(geo).y < center(current_geo).y).then_some((
geo.loc.y + geo.size.h,
(center(geo).x - center(current_geo).x).abs(),
))
})
}

pub fn output_down_of(&self, current: &Output) -> Option<Output> {
Expand All @@ -3517,13 +3558,10 @@ impl Niri {
Size::from((current_geo.size.w, i32::MAX)),
);

self.global_space
.outputs()
.map(|output| (output, self.global_space.output_geometry(output).unwrap()))
.filter(|(_, geo)| center(*geo).y > center(current_geo).y && geo.overlaps(extended_geo))
.min_by_key(|(_, geo)| center(*geo).y - center(current_geo).y)
.map(|(output, _)| output)
.cloned()
self.closest_output_in_direction(current, extended_geo, |current_geo, geo| {
(center(geo).y > center(current_geo).y)
.then_some((-geo.loc.y, (center(geo).x - center(current_geo).x).abs()))
})
}

pub fn output_previous_of(&self, current: &Output) -> Option<Output> {
Expand Down