mirror of
https://github.com/YaLTeR/niri.git
synced 2025-10-06 00:23:14 +02:00
wip
This commit is contained in:
@@ -23,7 +23,8 @@ use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
|
||||
use smithay::input::keyboard::{Keysym, XkbConfig};
|
||||
use smithay::reexports::input;
|
||||
|
||||
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::from_array_unpremul([0.2, 0.2, 0.2, 1.]);
|
||||
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::from_array_unpremul([0.25, 0.25, 0.25, 1.]);
|
||||
pub const DEFAULT_BACKDROP_COLOR: Color = Color::from_array_unpremul([0.15, 0.15, 0.15, 1.]);
|
||||
|
||||
pub mod layer_rule;
|
||||
|
||||
@@ -984,6 +985,8 @@ pub struct Animations {
|
||||
pub config_notification_open_close: ConfigNotificationOpenCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub screenshot_ui_open: ScreenshotUiOpenAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub overview_open_close: OverviewOpenCloseAnim,
|
||||
}
|
||||
|
||||
impl Default for Animations {
|
||||
@@ -999,6 +1002,7 @@ impl Default for Animations {
|
||||
window_resize: Default::default(),
|
||||
config_notification_open_close: Default::default(),
|
||||
screenshot_ui_open: Default::default(),
|
||||
overview_open_close: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1146,6 +1150,22 @@ impl Default for ScreenshotUiOpenAnim {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct OverviewOpenCloseAnim(pub Animation);
|
||||
|
||||
impl Default for OverviewOpenCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Spring(SpringParams {
|
||||
damping_ratio: 1.,
|
||||
stiffness: 800,
|
||||
epsilon: 0.0001,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Animation {
|
||||
pub off: bool,
|
||||
@@ -1183,6 +1203,8 @@ pub struct SpringParams {
|
||||
pub struct Gestures {
|
||||
#[knuffel(child, default)]
|
||||
pub dnd_edge_view_scroll: DndEdgeViewScroll,
|
||||
#[knuffel(child, default)]
|
||||
pub dnd_edge_workspace_switch: DndEdgeWorkspaceSwitch,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
@@ -1205,6 +1227,26 @@ impl Default for DndEdgeViewScroll {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct DndEdgeWorkspaceSwitch {
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().trigger_height)]
|
||||
pub trigger_height: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().delay_ms)]
|
||||
pub delay_ms: u16,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().max_speed)]
|
||||
pub max_speed: FloatOrInt<0, 1_000_000>,
|
||||
}
|
||||
|
||||
impl Default for DndEdgeWorkspaceSwitch {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
trigger_height: FloatOrInt(50.),
|
||||
delay_ms: 100,
|
||||
max_speed: FloatOrInt(1500.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct Environment(#[knuffel(children)] pub Vec<EnvironmentVariable>);
|
||||
|
||||
@@ -1716,6 +1758,7 @@ pub enum Action {
|
||||
SetDynamicCastWindowById(u64),
|
||||
SetDynamicCastMonitor(#[knuffel(argument)] Option<String>),
|
||||
ClearDynamicCastTarget,
|
||||
ToggleOverview,
|
||||
}
|
||||
|
||||
impl From<niri_ipc::Action> for Action {
|
||||
@@ -1980,6 +2023,7 @@ impl From<niri_ipc::Action> for Action {
|
||||
Self::SetDynamicCastMonitor(output)
|
||||
}
|
||||
niri_ipc::Action::ClearDynamicCastTarget {} => Self::ClearDynamicCastTarget,
|
||||
niri_ipc::Action::ToggleOverview {} => Self::ToggleOverview,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2964,6 +3008,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for OverviewOpenCloseAnim
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
{
|
||||
fn decode_node(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
let default = Self::default().0;
|
||||
Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Animation {
|
||||
pub fn new_off() -> Self {
|
||||
Self {
|
||||
@@ -4459,6 +4518,18 @@ mod tests {
|
||||
),
|
||||
},
|
||||
),
|
||||
overview_open_close: OverviewOpenCloseAnim(
|
||||
Animation {
|
||||
off: false,
|
||||
kind: Spring(
|
||||
SpringParams {
|
||||
damping_ratio: 1.0,
|
||||
stiffness: 800,
|
||||
epsilon: 0.0001,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
gestures: Gestures {
|
||||
dnd_edge_view_scroll: DndEdgeViewScroll {
|
||||
@@ -4470,6 +4541,15 @@ mod tests {
|
||||
50.0,
|
||||
),
|
||||
},
|
||||
dnd_edge_workspace_switch: DndEdgeWorkspaceSwitch {
|
||||
trigger_height: FloatOrInt(
|
||||
50.0,
|
||||
),
|
||||
delay_ms: 100,
|
||||
max_speed: FloatOrInt(
|
||||
1500.0,
|
||||
),
|
||||
},
|
||||
},
|
||||
environment: Environment(
|
||||
[
|
||||
|
@@ -764,6 +764,8 @@ pub enum Action {
|
||||
},
|
||||
/// Clear the dynamic cast target, making it show nothing.
|
||||
ClearDynamicCastTarget {},
|
||||
/// Toggle the Overview.
|
||||
ToggleOverview {},
|
||||
}
|
||||
|
||||
/// Change in window or column size.
|
||||
|
@@ -266,6 +266,7 @@ impl TestCase for Layout {
|
||||
.monitor_for_output(&self.output)
|
||||
.unwrap()
|
||||
.render_elements(renderer, RenderTarget::Output, true)
|
||||
.flat_map(|(_, iter)| iter)
|
||||
.map(|elem| Box::new(elem) as _)
|
||||
.collect()
|
||||
}
|
||||
|
@@ -153,7 +153,7 @@ impl XdgShellHandler for State {
|
||||
|
||||
match start_data {
|
||||
PointerOrTouchStartData::Pointer(start_data) => {
|
||||
let grab = MoveGrab::new(start_data, window);
|
||||
let grab = MoveGrab::new(start_data, window, false);
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
}
|
||||
PointerOrTouchStartData::Touch(start_data) => {
|
||||
@@ -316,6 +316,9 @@ impl XdgShellHandler for State {
|
||||
} else if let Some(output) = self.niri.layout.active_output() {
|
||||
let layers = layer_map_for_output(output);
|
||||
|
||||
// FIXME: somewhere here we probably need to check is_overview_open to match the logic
|
||||
// in update_keyboard_focus().
|
||||
|
||||
if layers
|
||||
.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL)
|
||||
.is_none()
|
||||
|
267
src/input/mod.rs
267
src/input/mod.rs
@@ -29,7 +29,7 @@ use smithay::input::touch::{
|
||||
};
|
||||
use smithay::input::SeatHandler;
|
||||
use smithay::output::Output;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Transform, SERIAL_COUNTER};
|
||||
use smithay::utils::{Logical, Point, Rectangle, Size, Transform, SERIAL_COUNTER};
|
||||
use smithay::wayland::keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitor;
|
||||
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint};
|
||||
use smithay::wayland::selection::data_device::DnDGrab;
|
||||
@@ -393,6 +393,15 @@ impl State {
|
||||
return FilterResult::Intercept(None);
|
||||
}
|
||||
|
||||
if this.niri.keyboard_focus.is_overview()
|
||||
&& pressed
|
||||
&& matches!(raw, Some(Keysym::Escape | Keysym::Return))
|
||||
{
|
||||
this.niri.layout.toggle_overview();
|
||||
this.niri.suppressed_keys.insert(key_code);
|
||||
return FilterResult::Intercept(None);
|
||||
}
|
||||
|
||||
let bindings = &this.niri.config.borrow().binds;
|
||||
should_intercept_key(
|
||||
&mut this.niri.suppressed_keys,
|
||||
@@ -1915,10 +1924,18 @@ impl State {
|
||||
Action::ClearDynamicCastTarget => {
|
||||
self.set_dynamic_cast_target(CastTarget::Nothing);
|
||||
}
|
||||
Action::ToggleOverview => {
|
||||
self.niri.layout.toggle_overview();
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_pointer_motion<I: InputBackend>(&mut self, event: I::PointerMotionEvent) {
|
||||
let was_inside_hot_corner = self.niri.pointer_inside_hot_corner;
|
||||
// Any of the early returns here mean that the pointer is not inside the hot corner.
|
||||
self.niri.pointer_inside_hot_corner = false;
|
||||
|
||||
// We need an output to be able to move the pointer.
|
||||
if self.niri.global_space.outputs().next().is_none() {
|
||||
return;
|
||||
@@ -2095,6 +2112,18 @@ impl State {
|
||||
|
||||
pointer.frame(self);
|
||||
|
||||
// contents_under() will return no surface when the hot corner should trigger.
|
||||
if pointer.current_focus().is_none() {
|
||||
let hot_corner = Rectangle::from_size(Size::from((1., 1.)));
|
||||
if let Some((_, pos_within_output)) = self.niri.output_under(pos) {
|
||||
let inside_hot_corner = hot_corner.contains(pos_within_output);
|
||||
if inside_hot_corner && !was_inside_hot_corner {
|
||||
self.niri.layout.toggle_overview();
|
||||
}
|
||||
self.niri.pointer_inside_hot_corner = inside_hot_corner;
|
||||
}
|
||||
}
|
||||
|
||||
// Activate a new confinement if necessary.
|
||||
self.niri.maybe_activate_pointer_constraint();
|
||||
|
||||
@@ -2119,6 +2148,10 @@ impl State {
|
||||
&mut self,
|
||||
event: I::PointerMotionAbsoluteEvent,
|
||||
) {
|
||||
let was_inside_hot_corner = self.niri.pointer_inside_hot_corner;
|
||||
// Any of the early returns here mean that the pointer is not inside the hot corner.
|
||||
self.niri.pointer_inside_hot_corner = false;
|
||||
|
||||
let Some(pos) = self.compute_absolute_location(&event, None).or_else(|| {
|
||||
self.global_bounding_rectangle().map(|output_geo| {
|
||||
event.position_transformed(output_geo.size) + output_geo.loc.to_f64()
|
||||
@@ -2164,6 +2197,18 @@ impl State {
|
||||
|
||||
pointer.frame(self);
|
||||
|
||||
// contents_under() will return no surface when the hot corner should trigger.
|
||||
if pointer.current_focus().is_none() {
|
||||
let hot_corner = Rectangle::from_size(Size::from((1., 1.)));
|
||||
if let Some((_, pos_within_output)) = self.niri.output_under(pos) {
|
||||
let inside_hot_corner = hot_corner.contains(pos_within_output);
|
||||
if inside_hot_corner && !was_inside_hot_corner {
|
||||
self.niri.layout.toggle_overview();
|
||||
}
|
||||
self.niri.pointer_inside_hot_corner = inside_hot_corner;
|
||||
}
|
||||
}
|
||||
|
||||
self.niri.maybe_activate_pointer_constraint();
|
||||
|
||||
// We moved the pointer, show it.
|
||||
@@ -2235,10 +2280,54 @@ impl State {
|
||||
self.niri.pointer_hidden = false;
|
||||
self.niri.tablet_cursor_location = None;
|
||||
|
||||
let is_overview_open = self.niri.layout.is_overview_open();
|
||||
|
||||
if is_overview_open && !pointer.is_grabbed() && button == Some(MouseButton::Right) {
|
||||
if let Some((output, ws)) = self.niri.workspace_under_cursor(true) {
|
||||
let ws_id = ws.id();
|
||||
let ws_idx = self.niri.layout.find_workspace_by_id(ws_id).unwrap().0;
|
||||
|
||||
self.niri.layout.focus_output(&output);
|
||||
|
||||
let location = pointer.current_location();
|
||||
let start_data = PointerGrabStartData {
|
||||
focus: None,
|
||||
button: button_code,
|
||||
location,
|
||||
};
|
||||
self.niri
|
||||
.layout
|
||||
.view_offset_gesture_begin(&output, Some(ws_idx), false);
|
||||
let grab = SpatialMovementGrab::new(start_data, output, ws_id, true);
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
self.niri
|
||||
.cursor_manager
|
||||
.set_cursor_image(CursorImageStatus::Named(CursorIcon::AllScroll));
|
||||
|
||||
// FIXME: granular.
|
||||
self.niri.queue_redraw_all();
|
||||
|
||||
// Don't activate the window under the cursor to avoid unnecessary
|
||||
// scrolling when e.g. Mod+MMB clicking on a partially off-screen window.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if button == Some(MouseButton::Middle) && !pointer.is_grabbed() {
|
||||
let mod_down = modifiers_from_state(mods).contains(mod_key.to_modifiers());
|
||||
if mod_down {
|
||||
if let Some(output) = self.niri.output_under_cursor() {
|
||||
let output_ws = if is_overview_open {
|
||||
self.niri.workspace_under_cursor(true)
|
||||
} else {
|
||||
self.niri.output_under_cursor().and_then(|output| {
|
||||
let mon = self.niri.layout.monitor_for_output(&output)?;
|
||||
Some((output, mon.active_workspace_ref()))
|
||||
})
|
||||
};
|
||||
|
||||
if let Some((output, ws)) = output_ws {
|
||||
let ws_id = ws.id();
|
||||
|
||||
self.niri.layout.focus_output(&output);
|
||||
|
||||
let location = pointer.current_location();
|
||||
@@ -2247,7 +2336,7 @@ impl State {
|
||||
button: button_code,
|
||||
location,
|
||||
};
|
||||
let grab = SpatialMovementGrab::new(start_data, output);
|
||||
let grab = SpatialMovementGrab::new(start_data, output, ws_id, false);
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
self.niri
|
||||
.cursor_manager
|
||||
@@ -2269,12 +2358,14 @@ impl State {
|
||||
// Check if we need to start an interactive move.
|
||||
if button == Some(MouseButton::Left) && !pointer.is_grabbed() {
|
||||
let mod_down = modifiers_from_state(mods).contains(mod_key.to_modifiers());
|
||||
if mod_down {
|
||||
if is_overview_open || mod_down {
|
||||
let location = pointer.current_location();
|
||||
let (output, pos_within_output) = self.niri.output_under(location).unwrap();
|
||||
let output = output.clone();
|
||||
|
||||
self.niri.layout.activate_window(&window);
|
||||
if !is_overview_open {
|
||||
self.niri.layout.activate_window(&window);
|
||||
}
|
||||
|
||||
if self.niri.layout.interactive_move_begin(
|
||||
window.clone(),
|
||||
@@ -2286,11 +2377,14 @@ impl State {
|
||||
button: button_code,
|
||||
location,
|
||||
};
|
||||
let grab = MoveGrab::new(start_data, window.clone());
|
||||
let grab = MoveGrab::new(start_data, window.clone(), is_overview_open);
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
self.niri
|
||||
.cursor_manager
|
||||
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Move));
|
||||
|
||||
if !is_overview_open {
|
||||
self.niri
|
||||
.cursor_manager
|
||||
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Move));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2365,7 +2459,20 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
self.niri.layout.activate_window(&window);
|
||||
if !is_overview_open {
|
||||
self.niri.layout.activate_window(&window);
|
||||
}
|
||||
|
||||
// FIXME: granular.
|
||||
self.niri.queue_redraw_all();
|
||||
} else if let Some((output, ws)) = is_overview_open
|
||||
.then(|| self.niri.workspace_under_cursor(false))
|
||||
.flatten()
|
||||
{
|
||||
let ws_idx = self.niri.layout.find_workspace_by_id(ws.id()).unwrap().0;
|
||||
|
||||
self.niri.layout.focus_output(&output);
|
||||
self.niri.layout.toggle_overview_to_workspace(ws_idx);
|
||||
|
||||
// FIXME: granular.
|
||||
self.niri.queue_redraw_all();
|
||||
@@ -2437,23 +2544,63 @@ impl State {
|
||||
let horizontal_amount_v120 = event.amount_v120(Axis::Horizontal);
|
||||
let vertical_amount_v120 = event.amount_v120(Axis::Vertical);
|
||||
|
||||
let is_overview_open = self.niri.layout.is_overview_open();
|
||||
|
||||
// Handle wheel scroll bindings.
|
||||
if source == AxisSource::Wheel {
|
||||
// If we have a scroll bind with current modifiers, then accumulate and don't pass to
|
||||
// Wayland. If there's no bind, reset the accumulator.
|
||||
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
|
||||
let modifiers = modifiers_from_state(mods);
|
||||
if self.niri.mods_with_wheel_binds.contains(&modifiers) {
|
||||
let should_handle =
|
||||
is_overview_open || self.niri.mods_with_wheel_binds.contains(&modifiers);
|
||||
if should_handle {
|
||||
let horizontal = horizontal_amount_v120.unwrap_or(0.);
|
||||
let ticks = self.niri.horizontal_wheel_tracker.accumulate(horizontal);
|
||||
if ticks != 0 {
|
||||
let config = self.niri.config.borrow();
|
||||
let bindings = &config.binds;
|
||||
let bind_left =
|
||||
find_configured_bind(bindings, mod_key, Trigger::WheelScrollLeft, mods);
|
||||
let bind_right =
|
||||
find_configured_bind(bindings, mod_key, Trigger::WheelScrollRight, mods);
|
||||
drop(config);
|
||||
let (bind_left, bind_right) = if is_overview_open {
|
||||
if modifiers.is_empty() {
|
||||
let bind_left = Some(Bind {
|
||||
key: Key {
|
||||
trigger: Trigger::WheelScrollLeft,
|
||||
modifiers: Modifiers::empty(),
|
||||
},
|
||||
action: Action::FocusColumnLeft,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
allow_inhibiting: false,
|
||||
hotkey_overlay_title: None,
|
||||
});
|
||||
let bind_right = Some(Bind {
|
||||
key: Key {
|
||||
trigger: Trigger::WheelScrollRight,
|
||||
modifiers: Modifiers::empty(),
|
||||
},
|
||||
action: Action::FocusColumnRight,
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: false,
|
||||
allow_inhibiting: false,
|
||||
hotkey_overlay_title: None,
|
||||
});
|
||||
(bind_left, bind_right)
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
} else {
|
||||
let config = self.niri.config.borrow();
|
||||
let bindings = &config.binds;
|
||||
let bind_left =
|
||||
find_configured_bind(bindings, mod_key, Trigger::WheelScrollLeft, mods);
|
||||
let bind_right = find_configured_bind(
|
||||
bindings,
|
||||
mod_key,
|
||||
Trigger::WheelScrollRight,
|
||||
mods,
|
||||
);
|
||||
(bind_left, bind_right)
|
||||
};
|
||||
|
||||
if let Some(right) = bind_right {
|
||||
for _ in 0..ticks {
|
||||
@@ -2470,13 +2617,45 @@ impl State {
|
||||
let vertical = vertical_amount_v120.unwrap_or(0.);
|
||||
let ticks = self.niri.vertical_wheel_tracker.accumulate(vertical);
|
||||
if ticks != 0 {
|
||||
let config = self.niri.config.borrow();
|
||||
let bindings = &config.binds;
|
||||
let bind_up =
|
||||
find_configured_bind(bindings, mod_key, Trigger::WheelScrollUp, mods);
|
||||
let bind_down =
|
||||
find_configured_bind(bindings, mod_key, Trigger::WheelScrollDown, mods);
|
||||
drop(config);
|
||||
let (bind_up, bind_down) = if is_overview_open {
|
||||
if modifiers.is_empty() {
|
||||
let bind_up = Some(Bind {
|
||||
key: Key {
|
||||
trigger: Trigger::WheelScrollUp,
|
||||
modifiers: Modifiers::empty(),
|
||||
},
|
||||
action: Action::FocusWorkspaceUp,
|
||||
repeat: true,
|
||||
cooldown: Some(Duration::from_millis(50)),
|
||||
allow_when_locked: false,
|
||||
allow_inhibiting: false,
|
||||
hotkey_overlay_title: None,
|
||||
});
|
||||
let bind_down = Some(Bind {
|
||||
key: Key {
|
||||
trigger: Trigger::WheelScrollDown,
|
||||
modifiers: Modifiers::empty(),
|
||||
},
|
||||
action: Action::FocusWorkspaceDown,
|
||||
repeat: true,
|
||||
cooldown: Some(Duration::from_millis(50)),
|
||||
allow_when_locked: false,
|
||||
allow_inhibiting: false,
|
||||
hotkey_overlay_title: None,
|
||||
});
|
||||
(bind_up, bind_down)
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
} else {
|
||||
let config = self.niri.config.borrow();
|
||||
let bindings = &config.binds;
|
||||
let bind_up =
|
||||
find_configured_bind(bindings, mod_key, Trigger::WheelScrollUp, mods);
|
||||
let bind_down =
|
||||
find_configured_bind(bindings, mod_key, Trigger::WheelScrollDown, mods);
|
||||
(bind_up, bind_down)
|
||||
};
|
||||
|
||||
if let Some(down) = bind_down {
|
||||
for _ in 0..ticks {
|
||||
@@ -2771,6 +2950,12 @@ impl State {
|
||||
if event.fingers() == 3 {
|
||||
self.niri.gesture_swipe_3f_cumulative = Some((0., 0.));
|
||||
|
||||
// We handled this event.
|
||||
return;
|
||||
} else if event.fingers() == 4 {
|
||||
self.niri.layout.overview_gesture_begin();
|
||||
self.niri.queue_redraw_all();
|
||||
|
||||
// We handled this event.
|
||||
return;
|
||||
}
|
||||
@@ -2816,6 +3001,8 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
let is_overview_open = self.niri.layout.is_overview_open();
|
||||
|
||||
if let Some((cx, cy)) = &mut self.niri.gesture_swipe_3f_cumulative {
|
||||
*cx += delta_x;
|
||||
*cy += delta_y;
|
||||
@@ -2827,7 +3014,21 @@ impl State {
|
||||
|
||||
if let Some(output) = self.niri.output_under_cursor() {
|
||||
if cx.abs() > cy.abs() {
|
||||
self.niri.layout.view_offset_gesture_begin(&output, true);
|
||||
let output_ws = if is_overview_open {
|
||||
self.niri.workspace_under_cursor(true)
|
||||
} else {
|
||||
self.niri.output_under_cursor().and_then(|output| {
|
||||
let mon = self.niri.layout.monitor_for_output(&output)?;
|
||||
Some((output, mon.active_workspace_ref()))
|
||||
})
|
||||
};
|
||||
|
||||
if let Some((output, ws)) = output_ws {
|
||||
let ws_idx = self.niri.layout.find_workspace_by_id(ws.id()).unwrap().0;
|
||||
self.niri
|
||||
.layout
|
||||
.view_offset_gesture_begin(&output, Some(ws_idx), true);
|
||||
}
|
||||
} else {
|
||||
self.niri
|
||||
.layout
|
||||
@@ -2862,6 +3063,14 @@ impl State {
|
||||
handled = true;
|
||||
}
|
||||
|
||||
let res = self.niri.layout.overview_gesture_update(delta_y, timestamp);
|
||||
if let Some(redraw) = res {
|
||||
if redraw {
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if handled {
|
||||
// We handled this event.
|
||||
return;
|
||||
@@ -2904,6 +3113,12 @@ impl State {
|
||||
handled = true;
|
||||
}
|
||||
|
||||
let res = self.niri.layout.overview_gesture_end();
|
||||
if res {
|
||||
self.niri.queue_redraw_all();
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if handled {
|
||||
// We handled this event.
|
||||
return;
|
||||
|
@@ -1,10 +1,11 @@
|
||||
use smithay::backend::input::ButtonState;
|
||||
use smithay::desktop::Window;
|
||||
use smithay::input::pointer::{
|
||||
AxisFrame, ButtonEvent, CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||
MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent,
|
||||
AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, GestureHoldBeginEvent,
|
||||
GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
|
||||
RelativeMotionEvent,
|
||||
};
|
||||
use smithay::input::SeatHandler;
|
||||
use smithay::utils::{IsAlive, Logical, Point};
|
||||
@@ -15,14 +16,32 @@ pub struct MoveGrab {
|
||||
start_data: PointerGrabStartData<State>,
|
||||
last_location: Point<f64, Logical>,
|
||||
window: Window,
|
||||
gesture: GestureState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum GestureState {
|
||||
Recognizing,
|
||||
Move,
|
||||
}
|
||||
|
||||
impl MoveGrab {
|
||||
pub fn new(start_data: PointerGrabStartData<State>, window: Window) -> Self {
|
||||
pub fn new(
|
||||
start_data: PointerGrabStartData<State>,
|
||||
window: Window,
|
||||
use_threshold: bool,
|
||||
) -> Self {
|
||||
let gesture = if use_threshold {
|
||||
GestureState::Recognizing
|
||||
} else {
|
||||
GestureState::Move
|
||||
};
|
||||
|
||||
Self {
|
||||
last_location: start_data.location,
|
||||
start_data,
|
||||
window,
|
||||
gesture,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +72,24 @@ impl PointerGrab<State> for MoveGrab {
|
||||
let output = output.clone();
|
||||
let event_delta = event.location - self.last_location;
|
||||
self.last_location = event.location;
|
||||
|
||||
if self.gesture == GestureState::Recognizing {
|
||||
let c = event.location - self.start_data.location;
|
||||
|
||||
// Check if the gesture moved far enough to decide.
|
||||
if c.x * c.x + c.y * c.y >= 8. * 8. {
|
||||
self.gesture = GestureState::Move;
|
||||
|
||||
data.niri
|
||||
.cursor_manager
|
||||
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Move));
|
||||
}
|
||||
}
|
||||
|
||||
if self.gesture != GestureState::Move {
|
||||
return;
|
||||
}
|
||||
|
||||
let ongoing = data.niri.layout.interactive_move_update(
|
||||
&self.window,
|
||||
event_delta,
|
||||
|
@@ -10,12 +10,14 @@ use smithay::input::SeatHandler;
|
||||
use smithay::output::Output;
|
||||
use smithay::utils::{Logical, Point};
|
||||
|
||||
use crate::layout::workspace::WorkspaceId;
|
||||
use crate::niri::State;
|
||||
|
||||
pub struct SpatialMovementGrab {
|
||||
start_data: PointerGrabStartData<State>,
|
||||
last_location: Point<f64, Logical>,
|
||||
output: Output,
|
||||
workspace_id: WorkspaceId,
|
||||
gesture: GestureState,
|
||||
}
|
||||
|
||||
@@ -27,12 +29,24 @@ enum GestureState {
|
||||
}
|
||||
|
||||
impl SpatialMovementGrab {
|
||||
pub fn new(start_data: PointerGrabStartData<State>, output: Output) -> Self {
|
||||
pub fn new(
|
||||
start_data: PointerGrabStartData<State>,
|
||||
output: Output,
|
||||
workspace_id: WorkspaceId,
|
||||
is_view_offset: bool,
|
||||
) -> Self {
|
||||
let gesture = if is_view_offset {
|
||||
GestureState::ViewOffset
|
||||
} else {
|
||||
GestureState::Recognizing
|
||||
};
|
||||
|
||||
Self {
|
||||
last_location: start_data.location,
|
||||
start_data,
|
||||
output,
|
||||
gesture: GestureState::Recognizing,
|
||||
workspace_id,
|
||||
gesture,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +95,16 @@ impl PointerGrab<State> for SpatialMovementGrab {
|
||||
if c.x * c.x + c.y * c.y >= 8. * 8. {
|
||||
if c.x.abs() > c.y.abs() {
|
||||
self.gesture = GestureState::ViewOffset;
|
||||
layout.view_offset_gesture_begin(&self.output, false);
|
||||
layout.view_offset_gesture_update(-c.x, timestamp, false)
|
||||
if let Some((ws_idx, ws)) = layout.find_workspace_by_id(self.workspace_id) {
|
||||
if ws.current_output() == Some(&self.output) {
|
||||
layout.view_offset_gesture_begin(&self.output, Some(ws_idx), false);
|
||||
layout.view_offset_gesture_update(-c.x, timestamp, false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
self.gesture = GestureState::WorkspaceSwitch;
|
||||
layout.workspace_switch_gesture_begin(&self.output, false);
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::backend::renderer::element::utils::{
|
||||
CropRenderElement, Relocate, RelocateRenderElement,
|
||||
CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement,
|
||||
};
|
||||
use smithay::output::Output;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Size};
|
||||
@@ -14,14 +14,16 @@ use super::tile::Tile;
|
||||
use super::workspace::{
|
||||
OutputId, Workspace, WorkspaceAddWindowTarget, WorkspaceId, WorkspaceRenderElement,
|
||||
};
|
||||
use super::{ActivateWindow, HitType, LayoutElement, Options};
|
||||
use super::{ActivateWindow, HitType, LayoutElement, Options, OVERVIEW_WORKSPACE_SCALE};
|
||||
use crate::animation::{Animation, Clock};
|
||||
use crate::input::swipe_tracker::SwipeTracker;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::shadow::ShadowRenderElement;
|
||||
use crate::render_helpers::RenderTarget;
|
||||
use crate::rubber_band::RubberBand;
|
||||
use crate::utils::transaction::Transaction;
|
||||
use crate::utils::{output_size, ResizeEdge};
|
||||
use crate::utils::{output_size, round_logical_in_physical, ResizeEdge};
|
||||
|
||||
/// Amount of touchpad movement to scroll the height of one workspace.
|
||||
const WORKSPACE_GESTURE_MOVEMENT: f64 = 300.;
|
||||
@@ -31,6 +33,11 @@ const WORKSPACE_GESTURE_RUBBER_BAND: RubberBand = RubberBand {
|
||||
limit: 0.05,
|
||||
};
|
||||
|
||||
/// Amount of DnD edge scrolling to scroll the height of one workspace.
|
||||
///
|
||||
/// This constant is tied to the default dnd-edge-workspace-switch max-speed setting.
|
||||
const WORKSPACE_DND_EDGE_SCROLL_MOVEMENT: f64 = 1500.;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Monitor<W: LayoutElement> {
|
||||
/// Output for this monitor.
|
||||
@@ -45,6 +52,10 @@ pub struct Monitor<W: LayoutElement> {
|
||||
pub(super) previous_workspace_id: Option<WorkspaceId>,
|
||||
/// In-progress switch between workspaces.
|
||||
pub(super) workspace_switch: Option<WorkspaceSwitch>,
|
||||
/// Whether the overview is open.
|
||||
pub(super) overview_open: bool,
|
||||
/// Progress of the overview zoom animation, 1 is fully in overview.
|
||||
overview_progress: Option<OverviewProgress>,
|
||||
/// Clock for driving animations.
|
||||
pub(super) clock: Clock,
|
||||
/// Configurable properties of the layout.
|
||||
@@ -66,6 +77,22 @@ pub struct WorkspaceSwitchGesture {
|
||||
tracker: SwipeTracker,
|
||||
/// Whether the gesture is controlled by the touchpad.
|
||||
is_touchpad: bool,
|
||||
/// Whether the gesture is clamped to +-1 workspace around the center.
|
||||
is_clamped: bool,
|
||||
|
||||
// If this gesture is for drag-and-drop scrolling, this is the last event's unadjusted
|
||||
// timestamp.
|
||||
dnd_last_event_time: Option<Duration>,
|
||||
// Time when the drag-and-drop scroll delta became non-zero, used for debouncing.
|
||||
//
|
||||
// If `None` then the scroll delta is currently zero.
|
||||
dnd_nonzero_start_time: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum OverviewProgress {
|
||||
Animation(Animation),
|
||||
Value(f64),
|
||||
}
|
||||
|
||||
/// Where to put a newly added window.
|
||||
@@ -85,8 +112,13 @@ pub enum MonitorAddWindowTarget<'a, W: LayoutElement> {
|
||||
NextTo(&'a W::Id),
|
||||
}
|
||||
|
||||
pub type MonitorRenderElement<R> =
|
||||
RelocateRenderElement<CropRenderElement<WorkspaceRenderElement<R>>>;
|
||||
niri_render_elements! {
|
||||
MonitorRenderElement<R> => {
|
||||
Workspace = RelocateRenderElement<RescaleRenderElement<CropRenderElement<
|
||||
WorkspaceRenderElement<R>>>>,
|
||||
Shadow = RelocateRenderElement<RescaleRenderElement<ShadowRenderElement>>,
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkspaceSwitch {
|
||||
pub fn current_idx(&self) -> f64 {
|
||||
@@ -126,6 +158,31 @@ impl WorkspaceSwitch {
|
||||
}
|
||||
}
|
||||
|
||||
impl OverviewProgress {
|
||||
pub fn value(&self) -> f64 {
|
||||
match self {
|
||||
OverviewProgress::Animation(anim) => anim.value(),
|
||||
OverviewProgress::Value(v) => *v,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamped_value(&self) -> f64 {
|
||||
match self {
|
||||
OverviewProgress::Animation(anim) => anim.clamped_value(),
|
||||
OverviewProgress::Value(v) => *v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&super::OverviewProgress> for OverviewProgress {
|
||||
fn from(value: &super::OverviewProgress) -> Self {
|
||||
match value {
|
||||
super::OverviewProgress::Animation(anim) => Self::Animation(anim.clone()),
|
||||
super::OverviewProgress::Gesture(gesture) => Self::Value(gesture.value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: LayoutElement> Monitor<W> {
|
||||
pub fn new(
|
||||
output: Output,
|
||||
@@ -139,6 +196,8 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
workspaces,
|
||||
active_workspace_idx: 0,
|
||||
previous_workspace_id: None,
|
||||
overview_open: false,
|
||||
overview_progress: None,
|
||||
workspace_switch: None,
|
||||
clock,
|
||||
options,
|
||||
@@ -212,17 +271,21 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
self.workspaces.push(ws);
|
||||
}
|
||||
|
||||
fn activate_workspace(&mut self, idx: usize) {
|
||||
pub fn activate_workspace(&mut self, idx: usize) {
|
||||
self.activate_workspace_with_anim_config(idx, None);
|
||||
}
|
||||
|
||||
pub fn activate_workspace_with_anim_config(
|
||||
&mut self,
|
||||
idx: usize,
|
||||
config: Option<niri_config::Animation>,
|
||||
) {
|
||||
if self.active_workspace_idx == idx {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: also compute and use current velocity.
|
||||
let current_idx = self
|
||||
.workspace_switch
|
||||
.as_ref()
|
||||
.map(|s| s.current_idx())
|
||||
.unwrap_or(self.active_workspace_idx as f64);
|
||||
let current_idx = self.workspace_render_idx();
|
||||
|
||||
self.previous_workspace_id = Some(self.workspaces[self.active_workspace_idx].id());
|
||||
|
||||
@@ -233,7 +296,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
current_idx,
|
||||
idx as f64,
|
||||
0.,
|
||||
self.options.animations.workspace_switch.0,
|
||||
config.unwrap_or(self.options.animations.workspace_switch.0),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -639,11 +702,32 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
pub fn advance_animations(&mut self) {
|
||||
if let Some(WorkspaceSwitch::Animation(anim)) = &mut self.workspace_switch {
|
||||
if anim.is_done() {
|
||||
self.workspace_switch = None;
|
||||
self.clean_up_workspaces();
|
||||
match &mut self.workspace_switch {
|
||||
Some(WorkspaceSwitch::Animation(anim)) => {
|
||||
if anim.is_done() {
|
||||
self.workspace_switch = None;
|
||||
self.clean_up_workspaces();
|
||||
}
|
||||
}
|
||||
Some(WorkspaceSwitch::Gesture(gesture)) => {
|
||||
// Make sure the last event time doesn't go too much out of date (for
|
||||
// monitors not under cursor), causing sudden jumps.
|
||||
//
|
||||
// This happens after any dnd_scroll_gesture_scroll() calls (in
|
||||
// Layout::advance_animations()), so it doesn't mess up the time delta there.
|
||||
if let Some(last_time) = &mut gesture.dnd_last_event_time {
|
||||
let now = self.clock.now_unadjusted();
|
||||
if *last_time != now {
|
||||
*last_time = now;
|
||||
|
||||
// If last_time was already == now, then dnd_scroll_gesture_scroll() must've
|
||||
// updated the gesture already. Therefore, when this code runs, the pointer
|
||||
// must be outside the DnD scrolling zone.
|
||||
gesture.dnd_nonzero_start_time = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
for ws in &mut self.workspaces {
|
||||
@@ -667,8 +751,9 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
pub fn update_render_elements(&mut self, is_active: bool) {
|
||||
let is_overview_open = self.overview_open;
|
||||
for (ws, _) in self.workspaces_with_render_geo_mut() {
|
||||
ws.update_render_elements(is_active);
|
||||
ws.update_render_elements(is_active, is_overview_open);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -804,6 +889,10 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
///
|
||||
/// During animations, assumes the final view position.
|
||||
pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
||||
if self.overview_open {
|
||||
return None;
|
||||
}
|
||||
// TODO: unify logic
|
||||
let mut rect = self.active_workspace_ref().active_tile_visual_rectangle()?;
|
||||
|
||||
if let Some(switch) = &self.workspace_switch {
|
||||
@@ -819,32 +908,140 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
Some(rect)
|
||||
}
|
||||
|
||||
pub fn workspaces_render_geo(&self) -> impl Iterator<Item = Rectangle<f64, Logical>> {
|
||||
let render_idx = if let Some(switch) = &self.workspace_switch {
|
||||
pub fn workspace_scale(&self) -> f64 {
|
||||
if let Some(p) = &self.overview_progress {
|
||||
(1. - p.value() * (1. - OVERVIEW_WORKSPACE_SCALE)).max(0.)
|
||||
} else {
|
||||
1.
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set_overview_progress(&mut self, progress: Option<&super::OverviewProgress>) {
|
||||
let prev_render_idx = self.workspace_render_idx();
|
||||
self.overview_progress = progress.map(OverviewProgress::from);
|
||||
let new_render_idx = self.workspace_render_idx();
|
||||
|
||||
// If the view jumped (can happen when going from corrected to uncorrected render_idx, for
|
||||
// example when toggling the overview in the middle of an overview animation), then restart
|
||||
// the workspace switch to avoid jumps.
|
||||
if prev_render_idx != new_render_idx {
|
||||
if let Some(WorkspaceSwitch::Animation(anim)) = &mut self.workspace_switch {
|
||||
// FIXME: maintain velocity.
|
||||
*anim = anim.restarted(prev_render_idx, anim.to(), 0.);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn overview_progress_value(&self) -> Option<f64> {
|
||||
self.overview_progress.as_ref().map(|p| p.value())
|
||||
}
|
||||
|
||||
pub fn workspace_render_idx(&self) -> f64 {
|
||||
// If workspace switch and overview progress are matching animations, then compute a
|
||||
// correction term to make the movement appear monotonic.
|
||||
if let (
|
||||
Some(WorkspaceSwitch::Animation(switch_anim)),
|
||||
Some(OverviewProgress::Animation(progress_anim)),
|
||||
) = (&self.workspace_switch, &self.overview_progress)
|
||||
{
|
||||
if switch_anim.start_time() == progress_anim.start_time()
|
||||
&& (switch_anim.duration().as_secs_f64() - progress_anim.duration().as_secs_f64())
|
||||
.abs()
|
||||
<= 0.001
|
||||
{
|
||||
let scale = self.output.current_scale().fractional_scale();
|
||||
let size = output_size(&self.output);
|
||||
|
||||
#[rustfmt::skip]
|
||||
// How this was derived:
|
||||
//
|
||||
// - Assume we're animating a zoom + switch. Consider switch "from" and "to".
|
||||
// These are render_idx values, so first workspace to second would have switch
|
||||
// from = 0. and to = 1. regardless of the zoom level.
|
||||
//
|
||||
// - At the start, the point at "from" is at Y = 0. We're moving the point at "to"
|
||||
// to Y = 0. We want this to be a monotonic motion in apparent coordinates (after
|
||||
// zoom).
|
||||
//
|
||||
// - Height at the start:
|
||||
// from_height = (size.h + gap) * from_ws_scale.
|
||||
//
|
||||
// - Current height:
|
||||
// current_height = (size.h + gap) * ws_scale.
|
||||
//
|
||||
// - We're moving the "to" point to Y = 0:
|
||||
// to_y = 0.
|
||||
//
|
||||
// - The initial position of the point we're moving:
|
||||
// from_y = (to - from) * from_height.
|
||||
//
|
||||
// - We want this point to travel monotonically in apparent coordinates:
|
||||
// current_y = from_y + (to_y - from_y) * progress,
|
||||
// where progress is from 0 to 1, equals to the animation progress (switch and
|
||||
// zoom are the same since they are synchronized).
|
||||
//
|
||||
// - Derive the Y of the first workspace from this:
|
||||
// first_y = current_y - to * current_height.
|
||||
//
|
||||
// Now, let's substitute and rearrange the terms.
|
||||
//
|
||||
// - current_y = from_y + (0 - (to - from) * from_height) * progress
|
||||
// - progress = (switch_anim.value() - from) / (to - from)
|
||||
// - current_y = from_y - (to - from) * from_height * (switch_anim.value() - from) / (to - from)
|
||||
// - current_y = from_y - from_height * (switch_anim.value() - from)
|
||||
// - first_y = from_y - from_height * (switch_anim.value() - from) - to * current_height
|
||||
// - first_y = (to - from) * from_height - from_height * (switch_anim.value() - from) - to * current_height
|
||||
// - first_y = to * from_height - switch_anim.value() * from_height - to * current_height
|
||||
// - first_y = -switch_anim.value() * from_height + to * (from_height - current_height)
|
||||
let from = progress_anim.from();
|
||||
let from_ws_scale = (1. - from * (1. - OVERVIEW_WORKSPACE_SCALE)).max(0.);
|
||||
let from_ws_height = round_logical_in_physical(scale, size.h * from_ws_scale);
|
||||
let from_gap = round_logical_in_physical(scale, size.h * from_ws_scale * 0.1);
|
||||
let from_ws_height_gap = from_ws_height + from_gap;
|
||||
|
||||
let ws_scale = self.workspace_scale();
|
||||
let ws_height = round_logical_in_physical(scale, size.h * ws_scale);
|
||||
let gap = round_logical_in_physical(scale, size.h * ws_scale * 0.1);
|
||||
let ws_height_gap = ws_height + gap;
|
||||
|
||||
let first_ws_y = -switch_anim.value() * from_ws_height_gap
|
||||
+ switch_anim.to() * (from_ws_height_gap - ws_height_gap);
|
||||
|
||||
return -first_ws_y / ws_height_gap;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(switch) = &self.workspace_switch {
|
||||
switch.current_idx()
|
||||
} else {
|
||||
self.active_workspace_idx as f64
|
||||
};
|
||||
|
||||
let before_idx = render_idx.floor();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn workspaces_render_geo(&self) -> impl Iterator<Item = Rectangle<f64, Logical>> {
|
||||
let scale = self.output.current_scale().fractional_scale();
|
||||
let size = output_size(&self.output);
|
||||
let ws_scale = self.workspace_scale();
|
||||
|
||||
// Compute the offset in such a way that if render_idx is active_workspace_idx, then its
|
||||
// offset will be (0., 0.).
|
||||
let before_ws_y = (before_idx - render_idx) * size.h;
|
||||
|
||||
// Ceil the workspace size in physical pixels.
|
||||
let ws_size = Size::from((size.w, size.h))
|
||||
let ws_size = size
|
||||
.upscale(ws_scale)
|
||||
.to_physical_precise_ceil(scale)
|
||||
.to_logical(scale);
|
||||
|
||||
let first_ws_y = before_ws_y - ws_size.h * before_idx;
|
||||
let gap = round_logical_in_physical(scale, size.h * 0.1 * ws_scale);
|
||||
let ws_height_gap = ws_size.h + gap;
|
||||
|
||||
let static_offset = (size.to_point() - ws_size.to_point()).downscale(2.);
|
||||
// let static_offset = Point::from((0., 0.));
|
||||
|
||||
// Compute the offset in such a way that if render_idx is active_workspace_idx and the
|
||||
// workspace scale is 1., then its offset will be (0., 0.).
|
||||
let first_ws_y = -self.workspace_render_idx() * ws_height_gap;
|
||||
|
||||
(0..self.workspaces.len()).map(move |idx| {
|
||||
let y = first_ws_y + idx as f64 * ws_size.h;
|
||||
let loc = Point::from((0., y));
|
||||
let y = first_ws_y + idx as f64 * ws_height_gap;
|
||||
let loc = Point::from((0., y)) + static_offset;
|
||||
let loc = loc.to_physical_precise_round(scale).to_logical(scale);
|
||||
Rectangle::new(loc, ws_size)
|
||||
})
|
||||
@@ -890,20 +1087,42 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
Some((ws, geo))
|
||||
}
|
||||
|
||||
pub fn workspace_under_narrow(
|
||||
&self,
|
||||
pos_within_output: Point<f64, Logical>,
|
||||
) -> Option<&Workspace<W>> {
|
||||
self.workspaces_with_render_geo()
|
||||
.find_map(|(ws, geo)| geo.contains(pos_within_output).then_some(ws))
|
||||
}
|
||||
|
||||
pub fn window_under(&self, pos_within_output: Point<f64, Logical>) -> Option<(&W, HitType)> {
|
||||
let (ws, geo) = self.workspace_under(pos_within_output)?;
|
||||
let (win, hit) = ws.window_under(pos_within_output - geo.loc)?;
|
||||
Some((win, hit.offset_win_pos(geo.loc)))
|
||||
|
||||
if self.overview_progress.is_some() {
|
||||
let ws_scale = self.workspace_scale().max(0.0001);
|
||||
let pos_within_workspace = (pos_within_output - geo.loc).downscale(ws_scale);
|
||||
let (win, hit) = ws.window_under(pos_within_workspace)?;
|
||||
// During the overview animation, we cannot do input hits because we cannot really
|
||||
// represent scaled windows properly.
|
||||
Some((win, hit.to_activate()))
|
||||
} else {
|
||||
let (win, hit) = ws.window_under(pos_within_output - geo.loc)?;
|
||||
Some((win, hit.offset_win_pos(geo.loc)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize_edges_under(&self, pos_within_output: Point<f64, Logical>) -> Option<ResizeEdge> {
|
||||
if self.overview_progress.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (ws, geo) = self.workspace_under(pos_within_output)?;
|
||||
ws.resize_edges_under(pos_within_output - geo.loc)
|
||||
}
|
||||
|
||||
pub fn render_above_top_layer(&self) -> bool {
|
||||
// Render above the top layer only if the view is stationary.
|
||||
if self.workspace_switch.is_some() {
|
||||
if self.workspace_switch.is_some() || self.overview_progress.is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -916,7 +1135,12 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
renderer: &'a mut R,
|
||||
target: RenderTarget,
|
||||
focus_ring: bool,
|
||||
) -> impl Iterator<Item = MonitorRenderElement<R>> + 'a {
|
||||
) -> impl Iterator<
|
||||
Item = (
|
||||
Rectangle<f64, Logical>,
|
||||
impl Iterator<Item = MonitorRenderElement<R>>,
|
||||
),
|
||||
> + 'a {
|
||||
let _span = tracy_client::span!("Monitor::render_elements");
|
||||
|
||||
let scale = self.output.current_scale().fractional_scale();
|
||||
@@ -934,7 +1158,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
// rendering for maximized GTK windows.
|
||||
//
|
||||
// FIXME: use proper bounds after fixing the Crop element.
|
||||
let crop_bounds = if self.workspace_switch.is_some() {
|
||||
let crop_bounds = if self.workspace_switch.is_some() || self.overview_progress.is_some() {
|
||||
Rectangle::new(
|
||||
Point::from((-i32::MAX / 2, 0)),
|
||||
Size::from((i32::MAX, height)),
|
||||
@@ -946,38 +1170,102 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
)
|
||||
};
|
||||
|
||||
self.workspaces_with_render_geo()
|
||||
.flat_map(move |(ws, geo)| {
|
||||
ws.render_elements(renderer, target, focus_ring)
|
||||
.filter_map(move |elem| {
|
||||
CropRenderElement::from_element(elem, scale, crop_bounds)
|
||||
})
|
||||
.map(move |elem| {
|
||||
RelocateRenderElement::from_element(
|
||||
elem,
|
||||
// The offset we get from workspaces_with_render_positions() is already
|
||||
// rounded to physical pixels, but it's in the logical coordinate
|
||||
// space, so we need to convert it to physical.
|
||||
geo.loc.to_physical_precise_round(scale),
|
||||
Relocate::Relative,
|
||||
)
|
||||
})
|
||||
})
|
||||
let ws_scale = self.workspace_scale();
|
||||
let overview_clamped_progress = self.overview_progress.as_ref().map(|p| p.clamped_value());
|
||||
let is_overview_open = self.overview_open;
|
||||
self.workspaces_with_render_geo().map(move |(ws, geo)| {
|
||||
let iter = ws
|
||||
.render_elements(renderer, target, focus_ring, is_overview_open)
|
||||
.filter_map(move |elem| CropRenderElement::from_element(elem, scale, crop_bounds))
|
||||
// .map(move |elem| {
|
||||
// let elem_scale = 1. - (1. - ws_scale) / OVERVIEW_WORKSPACE_SCALE * 0.03;
|
||||
// RescaleRenderElement::from_element(
|
||||
// elem,
|
||||
// size.downscale(2.)
|
||||
// .to_physical_precise_round(scale)
|
||||
// .to_point(),
|
||||
// elem_scale,
|
||||
// )
|
||||
// })
|
||||
.map(move |elem| {
|
||||
RescaleRenderElement::from_element(elem, Point::from((0, 0)), ws_scale)
|
||||
})
|
||||
.map(move |elem| {
|
||||
RelocateRenderElement::from_element(
|
||||
elem,
|
||||
// The offset we get from workspaces_with_render_positions() is already
|
||||
// rounded to physical pixels, but it's in the logical coordinate
|
||||
// space, so we need to convert it to physical.
|
||||
geo.loc.to_physical_precise_round(scale),
|
||||
Relocate::Relative,
|
||||
)
|
||||
})
|
||||
.map(MonitorRenderElement::Workspace);
|
||||
let shadow = if let Some(value) = overview_clamped_progress {
|
||||
Vec::from_iter(
|
||||
ws.render_shadow(renderer)
|
||||
.map(move |elem| elem.with_alpha(value.clamp(0., 1.) as f32))
|
||||
.map(move |elem| {
|
||||
RescaleRenderElement::from_element(elem, Point::from((0, 0)), ws_scale)
|
||||
})
|
||||
.map(move |elem| {
|
||||
RelocateRenderElement::from_element(
|
||||
elem,
|
||||
geo.loc.to_physical_precise_round(scale),
|
||||
Relocate::Relative,
|
||||
)
|
||||
})
|
||||
.map(MonitorRenderElement::Shadow),
|
||||
)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
(geo, iter.chain(shadow))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn workspace_switch_gesture_begin(&mut self, is_touchpad: bool) {
|
||||
let center_idx = self.active_workspace_idx;
|
||||
let current_idx = self
|
||||
.workspace_switch
|
||||
.as_ref()
|
||||
.map(|s| s.current_idx())
|
||||
.unwrap_or(center_idx as f64);
|
||||
let current_idx = self.workspace_render_idx();
|
||||
|
||||
let gesture = WorkspaceSwitchGesture {
|
||||
center_idx,
|
||||
current_idx,
|
||||
tracker: SwipeTracker::new(),
|
||||
is_touchpad,
|
||||
is_clamped: !self.overview_open,
|
||||
dnd_last_event_time: None,
|
||||
dnd_nonzero_start_time: None,
|
||||
};
|
||||
self.workspace_switch = Some(WorkspaceSwitch::Gesture(gesture));
|
||||
}
|
||||
|
||||
pub fn dnd_scroll_gesture_begin(&mut self) {
|
||||
if let Some(WorkspaceSwitch::Gesture(WorkspaceSwitchGesture {
|
||||
dnd_last_event_time: Some(_),
|
||||
..
|
||||
})) = &self.workspace_switch
|
||||
{
|
||||
// Already active.
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.overview_open {
|
||||
// This gesture is only for the overview.
|
||||
return;
|
||||
}
|
||||
|
||||
let center_idx = self.active_workspace_idx;
|
||||
let current_idx = self.workspace_render_idx();
|
||||
|
||||
let gesture = WorkspaceSwitchGesture {
|
||||
center_idx,
|
||||
current_idx,
|
||||
tracker: SwipeTracker::new(),
|
||||
is_touchpad: false,
|
||||
is_clamped: false,
|
||||
dnd_last_event_time: Some(self.clock.now_unadjusted()),
|
||||
dnd_nonzero_start_time: None,
|
||||
};
|
||||
self.workspace_switch = Some(WorkspaceSwitch::Gesture(gesture));
|
||||
}
|
||||
@@ -988,27 +1276,46 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
timestamp: Duration,
|
||||
is_touchpad: bool,
|
||||
) -> Option<bool> {
|
||||
let ws_scale = self.workspace_scale().max(0.0001);
|
||||
|
||||
let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if gesture.is_touchpad != is_touchpad {
|
||||
if gesture.is_touchpad != is_touchpad || gesture.dnd_last_event_time.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Reduce the effect of ws_scale on the touchpad somewhat.
|
||||
let delta_scale = if gesture.is_touchpad {
|
||||
(ws_scale - 1.) / 2.5 + 1.
|
||||
} else {
|
||||
ws_scale
|
||||
};
|
||||
|
||||
let delta_y = delta_y / delta_scale;
|
||||
let mut rubber_band = WORKSPACE_GESTURE_RUBBER_BAND;
|
||||
rubber_band.limit /= ws_scale;
|
||||
|
||||
gesture.tracker.push(delta_y, timestamp);
|
||||
|
||||
let total_height = if gesture.is_touchpad {
|
||||
WORKSPACE_GESTURE_MOVEMENT
|
||||
} else {
|
||||
self.workspaces[0].view_size().h
|
||||
// Account for the gap.
|
||||
self.workspaces[0].view_size().h * 1.1
|
||||
};
|
||||
let pos = gesture.tracker.pos() / total_height;
|
||||
|
||||
let min = gesture.center_idx.saturating_sub(1) as f64;
|
||||
let max = (gesture.center_idx + 1).min(self.workspaces.len() - 1) as f64;
|
||||
let (min, max) = if gesture.is_clamped {
|
||||
let min = gesture.center_idx.saturating_sub(1) as f64;
|
||||
let max = (gesture.center_idx + 1).min(self.workspaces.len() - 1) as f64;
|
||||
(min, max)
|
||||
} else {
|
||||
(0., (self.workspaces.len() - 1) as f64)
|
||||
};
|
||||
let new_idx = gesture.center_idx as f64 + pos;
|
||||
let new_idx = WORKSPACE_GESTURE_RUBBER_BAND.clamp(min, max, new_idx);
|
||||
let new_idx = rubber_band.clamp(min, max, new_idx);
|
||||
|
||||
if gesture.current_idx == new_idx {
|
||||
return Some(false);
|
||||
@@ -1018,11 +1325,102 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
Some(true)
|
||||
}
|
||||
|
||||
pub fn dnd_scroll_gesture_scroll(&mut self, pos: Point<f64, Logical>, speed: f64) -> bool {
|
||||
let ws_scale = self.workspace_scale();
|
||||
|
||||
let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(last_time) = gesture.dnd_last_event_time else {
|
||||
// Not a DnD scroll.
|
||||
return false;
|
||||
};
|
||||
|
||||
let config = &self.options.gestures.dnd_edge_workspace_switch;
|
||||
let trigger_height = config.trigger_height.0;
|
||||
|
||||
// This working area intentionally does not include extra struts from Options.
|
||||
// TODO: working area
|
||||
let output_size = output_size(&self.output);
|
||||
|
||||
let width = output_size.w * ws_scale;
|
||||
let x = pos.x - (output_size.w - width) / 2.;
|
||||
|
||||
let y = pos.y;
|
||||
let height = output_size.h;
|
||||
|
||||
let y = y.clamp(0., height);
|
||||
let trigger_height = trigger_height.clamp(0., height / 2.);
|
||||
|
||||
let delta = if x < 0. || width <= x {
|
||||
// Outside the bounds horizontally.
|
||||
0.
|
||||
} else if y < trigger_height {
|
||||
-(trigger_height - y)
|
||||
} else if height - y < trigger_height {
|
||||
trigger_height - (height - y)
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
|
||||
let delta = if trigger_height < 0.01 {
|
||||
// Sanity check for trigger-height 0 or small window sizes.
|
||||
0.
|
||||
} else {
|
||||
// Normalize to [0, 1].
|
||||
delta / trigger_height
|
||||
};
|
||||
let delta = delta * speed;
|
||||
|
||||
let now = self.clock.now_unadjusted();
|
||||
gesture.dnd_last_event_time = Some(now);
|
||||
|
||||
if delta == 0. {
|
||||
// We're outside the scrolling zone.
|
||||
gesture.dnd_nonzero_start_time = None;
|
||||
return false;
|
||||
}
|
||||
|
||||
let nonzero_start = *gesture.dnd_nonzero_start_time.get_or_insert(now);
|
||||
|
||||
// Delay starting the gesture a bit to avoid unwanted movement when dragging across
|
||||
// monitors.
|
||||
let delay = Duration::from_millis(u64::from(config.delay_ms));
|
||||
if now.saturating_sub(nonzero_start) < delay {
|
||||
return true;
|
||||
}
|
||||
|
||||
let time_delta = now.saturating_sub(last_time).as_secs_f64();
|
||||
|
||||
let delta = delta * time_delta * config.max_speed.0;
|
||||
|
||||
gesture.tracker.push(delta, now);
|
||||
|
||||
let total_height = WORKSPACE_DND_EDGE_SCROLL_MOVEMENT;
|
||||
let pos = gesture.tracker.pos() / total_height;
|
||||
|
||||
let (min, max) = if gesture.is_clamped {
|
||||
let min = gesture.center_idx.saturating_sub(1) as f64;
|
||||
let max = (gesture.center_idx + 1).min(self.workspaces.len() - 1) as f64;
|
||||
(min, max)
|
||||
} else {
|
||||
(0., (self.workspaces.len() - 1) as f64)
|
||||
};
|
||||
let new_idx = gesture.center_idx as f64 + pos;
|
||||
let new_idx = new_idx.clamp(min, max);
|
||||
|
||||
gesture.current_idx = new_idx;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn workspace_switch_gesture_end(
|
||||
&mut self,
|
||||
cancelled: bool,
|
||||
is_touchpad: Option<bool>,
|
||||
) -> bool {
|
||||
let ws_scale = self.workspace_scale().max(0.0001);
|
||||
|
||||
let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
|
||||
return false;
|
||||
};
|
||||
@@ -1041,28 +1439,35 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
let now = self.clock.now_unadjusted();
|
||||
gesture.tracker.push(0., now);
|
||||
|
||||
let mut rubber_band = WORKSPACE_GESTURE_RUBBER_BAND;
|
||||
rubber_band.limit /= ws_scale;
|
||||
|
||||
let total_height = if gesture.is_touchpad {
|
||||
WORKSPACE_GESTURE_MOVEMENT
|
||||
} else if gesture.dnd_last_event_time.is_some() {
|
||||
WORKSPACE_DND_EDGE_SCROLL_MOVEMENT
|
||||
} else {
|
||||
self.workspaces[0].view_size().h
|
||||
// Account for the gap.
|
||||
self.workspaces[0].view_size().h * 1.1
|
||||
};
|
||||
|
||||
let mut velocity = gesture.tracker.velocity() / total_height;
|
||||
let current_pos = gesture.tracker.pos() / total_height;
|
||||
let pos = gesture.tracker.projected_end_pos() / total_height;
|
||||
|
||||
let min = gesture.center_idx.saturating_sub(1) as f64;
|
||||
let max = (gesture.center_idx + 1).min(self.workspaces.len() - 1) as f64;
|
||||
let (min, max) = if gesture.is_clamped {
|
||||
let min = gesture.center_idx.saturating_sub(1) as f64;
|
||||
let max = (gesture.center_idx + 1).min(self.workspaces.len() - 1) as f64;
|
||||
(min, max)
|
||||
} else {
|
||||
(0., (self.workspaces.len() - 1) as f64)
|
||||
};
|
||||
let new_idx = gesture.center_idx as f64 + pos;
|
||||
|
||||
let new_idx = WORKSPACE_GESTURE_RUBBER_BAND.clamp(min, max, new_idx);
|
||||
let new_idx = new_idx.clamp(min, max);
|
||||
let new_idx = new_idx.round() as usize;
|
||||
|
||||
velocity *= WORKSPACE_GESTURE_RUBBER_BAND.clamp_derivative(
|
||||
min,
|
||||
max,
|
||||
gesture.center_idx as f64 + current_pos,
|
||||
);
|
||||
velocity *= rubber_band.clamp_derivative(min, max, gesture.center_idx as f64 + current_pos);
|
||||
|
||||
self.previous_workspace_id = Some(self.workspaces[self.active_workspace_idx].id());
|
||||
|
||||
@@ -1077,4 +1482,19 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn dnd_scroll_gesture_end(&mut self) {
|
||||
if !matches!(
|
||||
self.workspace_switch,
|
||||
Some(WorkspaceSwitch::Gesture(WorkspaceSwitchGesture {
|
||||
dnd_last_event_time: Some(_),
|
||||
..
|
||||
}))
|
||||
) {
|
||||
// Not a DnD scroll.
|
||||
return;
|
||||
};
|
||||
|
||||
self.workspace_switch_gesture_end(false, None);
|
||||
}
|
||||
}
|
||||
|
@@ -371,7 +371,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
|| !self.closing_windows.is_empty()
|
||||
}
|
||||
|
||||
pub fn update_render_elements(&mut self, is_active: bool) {
|
||||
pub fn update_render_elements(&mut self, is_active: bool, is_overview_open: bool) {
|
||||
let view_pos = Point::from((self.view_pos(), 0.));
|
||||
let view_size = self.view_size;
|
||||
let active_idx = self.active_column_idx;
|
||||
@@ -384,7 +384,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
}
|
||||
|
||||
if let Some(insert_hint) = &self.insert_hint {
|
||||
if let Some(area) = self.insert_hint_area(insert_hint) {
|
||||
if let Some(area) = self.insert_hint_area(insert_hint, !is_overview_open) {
|
||||
let view_rect = Rectangle::new(area.loc.upscale(-1.), view_size);
|
||||
self.insert_hint_element.update_render_elements(
|
||||
area.size,
|
||||
@@ -2274,7 +2274,11 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_hint_area(&self, insert_hint: &InsertHint) -> Option<Rectangle<f64, Logical>> {
|
||||
fn insert_hint_area(
|
||||
&self,
|
||||
insert_hint: &InsertHint,
|
||||
clamp_to_view: bool,
|
||||
) -> Option<Rectangle<f64, Logical>> {
|
||||
let mut hint_area = match insert_hint.position {
|
||||
InsertPosition::NewColumn(column_index) => {
|
||||
if column_index == 0 || column_index == self.columns.len() {
|
||||
@@ -2369,7 +2373,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
let view_size = self.view_size;
|
||||
|
||||
// Make sure the hint is at least partially visible.
|
||||
if matches!(insert_hint.position, InsertPosition::NewColumn(_)) {
|
||||
if clamp_to_view && matches!(insert_hint.position, InsertPosition::NewColumn(_)) {
|
||||
hint_area.loc.x = hint_area.loc.x.max(-hint_area.size.w / 2.);
|
||||
hint_area.loc.x = hint_area.loc.x.min(view_size.w - hint_area.size.w / 2.);
|
||||
}
|
||||
@@ -2724,6 +2728,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
renderer: &mut R,
|
||||
target: RenderTarget,
|
||||
focus_ring: bool,
|
||||
is_overview_open: bool,
|
||||
) -> Vec<ScrollingSpaceRenderElement<R>> {
|
||||
let mut rv = vec![];
|
||||
|
||||
@@ -2731,7 +2736,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
|
||||
// Draw the insert hint.
|
||||
if let Some(insert_hint) = &self.insert_hint {
|
||||
if let Some(area) = self.insert_hint_area(insert_hint) {
|
||||
if let Some(area) = self.insert_hint_area(insert_hint, !is_overview_open) {
|
||||
rv.extend(
|
||||
self.insert_hint_element
|
||||
.render(renderer, area.loc)
|
||||
@@ -2916,14 +2921,14 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
Some(true)
|
||||
}
|
||||
|
||||
pub fn dnd_scroll_gesture_scroll(&mut self, delta: f64) {
|
||||
pub fn dnd_scroll_gesture_scroll(&mut self, delta: f64) -> bool {
|
||||
let ViewOffset::Gesture(gesture) = &mut self.view_offset else {
|
||||
return;
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(last_time) = gesture.dnd_last_event_time else {
|
||||
// Not a DnD scroll.
|
||||
return;
|
||||
return false;
|
||||
};
|
||||
|
||||
let config = &self.options.gestures.dnd_edge_view_scroll;
|
||||
@@ -2934,7 +2939,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
if delta == 0. {
|
||||
// We're outside the scrolling zone.
|
||||
gesture.dnd_nonzero_start_time = None;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let nonzero_start = *gesture.dnd_nonzero_start_time.get_or_insert(now);
|
||||
@@ -2943,7 +2948,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
// monitors.
|
||||
let delay = Duration::from_millis(u64::from(config.delay_ms));
|
||||
if now.saturating_sub(nonzero_start) < delay {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
let time_delta = now.saturating_sub(last_time).as_secs_f64();
|
||||
@@ -2987,6 +2992,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
|
||||
gesture.delta_from_tracker += clamped_offset - view_offset;
|
||||
gesture.current_view_offset = clamped_offset;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn view_offset_gesture_end(&mut self, _cancelled: bool, is_touchpad: Option<bool>) -> bool {
|
||||
|
@@ -576,6 +576,8 @@ enum Op {
|
||||
ViewOffsetGestureBegin {
|
||||
#[proptest(strategy = "1..=5usize")]
|
||||
output_idx: usize,
|
||||
#[proptest(strategy = "proptest::option::of(0..=4usize)")]
|
||||
workspace_idx: Option<usize>,
|
||||
is_touchpad: bool,
|
||||
},
|
||||
ViewOffsetGestureUpdate {
|
||||
@@ -602,6 +604,13 @@ enum Op {
|
||||
cancelled: bool,
|
||||
is_touchpad: Option<bool>,
|
||||
},
|
||||
OverviewGestureBegin,
|
||||
OverviewGestureUpdate {
|
||||
#[proptest(strategy = "-400f64..400f64")]
|
||||
delta: f64,
|
||||
timestamp: Duration,
|
||||
},
|
||||
OverviewGestureEnd,
|
||||
InteractiveMoveBegin {
|
||||
#[proptest(strategy = "1..=5usize")]
|
||||
window: usize,
|
||||
@@ -657,6 +666,7 @@ enum Op {
|
||||
#[proptest(strategy = "1..=5usize")]
|
||||
window: usize,
|
||||
},
|
||||
ToggleOverview,
|
||||
}
|
||||
|
||||
impl Op {
|
||||
@@ -1345,6 +1355,7 @@ impl Op {
|
||||
}
|
||||
Op::ViewOffsetGestureBegin {
|
||||
output_idx: id,
|
||||
workspace_idx,
|
||||
is_touchpad: normalize,
|
||||
} => {
|
||||
let name = format!("output{id}");
|
||||
@@ -1352,7 +1363,7 @@ impl Op {
|
||||
return;
|
||||
};
|
||||
|
||||
layout.view_offset_gesture_begin(&output, normalize);
|
||||
layout.view_offset_gesture_begin(&output, workspace_idx, normalize);
|
||||
}
|
||||
Op::ViewOffsetGestureUpdate {
|
||||
delta,
|
||||
@@ -1389,6 +1400,15 @@ impl Op {
|
||||
} => {
|
||||
layout.workspace_switch_gesture_end(cancelled, is_touchpad);
|
||||
}
|
||||
Op::OverviewGestureBegin => {
|
||||
layout.overview_gesture_begin();
|
||||
}
|
||||
Op::OverviewGestureUpdate { delta, timestamp } => {
|
||||
layout.overview_gesture_update(delta, timestamp);
|
||||
}
|
||||
Op::OverviewGestureEnd => {
|
||||
layout.overview_gesture_end();
|
||||
}
|
||||
Op::InteractiveMoveBegin {
|
||||
window,
|
||||
output_idx,
|
||||
@@ -1442,6 +1462,9 @@ impl Op {
|
||||
Op::InteractiveResizeEnd { window } => {
|
||||
layout.interactive_resize_end(&window);
|
||||
}
|
||||
Op::ToggleOverview => {
|
||||
layout.toggle_overview();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2265,6 +2288,7 @@ fn unfullscreen_view_offset_not_reset_on_gesture() {
|
||||
Op::FullscreenWindow(1),
|
||||
Op::ViewOffsetGestureBegin {
|
||||
output_idx: 1,
|
||||
workspace_idx: None,
|
||||
is_touchpad: true,
|
||||
},
|
||||
Op::ViewOffsetGestureEnd {
|
||||
|
@@ -2,7 +2,10 @@ use std::cmp::max;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use niri_config::{CenterFocusedColumn, OutputName, PresetSize, Workspace as WorkspaceConfig};
|
||||
use niri_config::{
|
||||
CenterFocusedColumn, CornerRadius, FloatOrInt, OutputName, PresetSize,
|
||||
Workspace as WorkspaceConfig,
|
||||
};
|
||||
use niri_ipc::{ColumnDisplay, PositionChange, SizeChange};
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::desktop::{layer_map_for_output, Window};
|
||||
@@ -18,6 +21,7 @@ use super::scrolling::{
|
||||
Column, ColumnWidth, InsertHint, InsertPosition, ScrollDirection, ScrollingSpace,
|
||||
ScrollingSpaceRenderElement,
|
||||
};
|
||||
use super::shadow::Shadow;
|
||||
use super::tile::{Tile, TileRenderSnapshot};
|
||||
use super::{
|
||||
ActivateWindow, HitType, InteractiveResizeData, LayoutElement, Options, RemovedTile, SizeFrac,
|
||||
@@ -25,6 +29,7 @@ use super::{
|
||||
use crate::animation::Clock;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::shadow::ShadowRenderElement;
|
||||
use crate::render_helpers::RenderTarget;
|
||||
use crate::utils::id::IdCounter;
|
||||
use crate::utils::transaction::{Transaction, TransactionBlocker};
|
||||
@@ -80,6 +85,9 @@ pub struct Workspace<W: LayoutElement> {
|
||||
/// zones.
|
||||
working_area: Rectangle<f64, Logical>,
|
||||
|
||||
/// This workspace's shadow in the overview.
|
||||
shadow: Shadow,
|
||||
|
||||
/// Clock for driving animations.
|
||||
pub(super) clock: Clock,
|
||||
|
||||
@@ -228,6 +236,17 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
options.clone(),
|
||||
);
|
||||
|
||||
let shadow_config = niri_config::Shadow {
|
||||
on: true,
|
||||
offset: niri_config::ShadowOffset {
|
||||
x: FloatOrInt(0.),
|
||||
y: FloatOrInt(20.),
|
||||
},
|
||||
softness: FloatOrInt(120.),
|
||||
spread: FloatOrInt(20.),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Self {
|
||||
scrolling,
|
||||
floating,
|
||||
@@ -237,6 +256,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
transform: output.current_transform(),
|
||||
view_size,
|
||||
working_area,
|
||||
shadow: Shadow::new(shadow_config),
|
||||
output: Some(output),
|
||||
clock,
|
||||
base_options,
|
||||
@@ -281,6 +301,17 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
options.clone(),
|
||||
);
|
||||
|
||||
let shadow_config = niri_config::Shadow {
|
||||
on: true,
|
||||
offset: niri_config::ShadowOffset {
|
||||
x: FloatOrInt(0.),
|
||||
y: FloatOrInt(20.),
|
||||
},
|
||||
softness: FloatOrInt(120.),
|
||||
spread: FloatOrInt(20.),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Self {
|
||||
scrolling,
|
||||
floating,
|
||||
@@ -291,6 +322,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
original_output,
|
||||
view_size,
|
||||
working_area,
|
||||
shadow: Shadow::new(shadow_config),
|
||||
clock,
|
||||
base_options,
|
||||
options,
|
||||
@@ -336,13 +368,23 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
self.scrolling.are_transitions_ongoing() || self.floating.are_transitions_ongoing()
|
||||
}
|
||||
|
||||
pub fn update_render_elements(&mut self, is_active: bool) {
|
||||
self.scrolling
|
||||
.update_render_elements(is_active && !self.floating_is_active.get());
|
||||
pub fn update_render_elements(&mut self, is_active: bool, is_overview_open: bool) {
|
||||
self.scrolling.update_render_elements(
|
||||
is_active && !self.floating_is_active.get(),
|
||||
is_overview_open,
|
||||
);
|
||||
|
||||
let view_rect = Rectangle::from_size(self.view_size);
|
||||
self.floating
|
||||
.update_render_elements(is_active && self.floating_is_active.get(), view_rect);
|
||||
|
||||
self.shadow.update_render_elements(
|
||||
self.view_size,
|
||||
true,
|
||||
CornerRadius::default(),
|
||||
self.scale.fractional_scale(),
|
||||
1.,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn update_config(&mut self, base_options: Rc<Options>) {
|
||||
@@ -370,6 +412,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
pub fn update_shaders(&mut self) {
|
||||
self.scrolling.update_shaders();
|
||||
self.floating.update_shaders();
|
||||
self.shadow.update_shaders();
|
||||
}
|
||||
|
||||
pub fn windows(&self) -> impl Iterator<Item = &W> + '_ {
|
||||
@@ -1409,11 +1452,15 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
renderer: &mut R,
|
||||
target: RenderTarget,
|
||||
focus_ring: bool,
|
||||
is_overview_open: bool,
|
||||
) -> impl Iterator<Item = WorkspaceRenderElement<R>> {
|
||||
let scrolling_focus_ring = focus_ring && !self.floating_is_active();
|
||||
let scrolling = self
|
||||
.scrolling
|
||||
.render_elements(renderer, target, scrolling_focus_ring);
|
||||
let scrolling = self.scrolling.render_elements(
|
||||
renderer,
|
||||
target,
|
||||
scrolling_focus_ring,
|
||||
is_overview_open,
|
||||
);
|
||||
let scrolling = scrolling.into_iter().map(WorkspaceRenderElement::from);
|
||||
|
||||
let floating_focus_ring = focus_ring && self.floating_is_active();
|
||||
@@ -1428,6 +1475,13 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
floating.into_iter().flatten().chain(scrolling)
|
||||
}
|
||||
|
||||
pub fn render_shadow<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
) -> impl Iterator<Item = ShadowRenderElement> + '_ {
|
||||
self.shadow.render(renderer, Point::from((0., 0.)))
|
||||
}
|
||||
|
||||
pub fn render_above_top_layer(&self) -> bool {
|
||||
self.scrolling.render_above_top_layer()
|
||||
}
|
||||
@@ -1628,7 +1682,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
self.scrolling.dnd_scroll_gesture_begin();
|
||||
}
|
||||
|
||||
pub fn dnd_scroll_gesture_scroll(&mut self, pos: Point<f64, Logical>) {
|
||||
pub fn dnd_scroll_gesture_scroll(&mut self, pos: Point<f64, Logical>, speed: f64) -> bool {
|
||||
let config = &self.options.gestures.dnd_edge_view_scroll;
|
||||
let trigger_width = config.trigger_width.0;
|
||||
|
||||
@@ -1654,8 +1708,9 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
// Normalize to [0, 1].
|
||||
delta / trigger_width
|
||||
};
|
||||
let delta = delta * speed;
|
||||
|
||||
self.scrolling.dnd_scroll_gesture_scroll(delta);
|
||||
self.scrolling.dnd_scroll_gesture_scroll(delta)
|
||||
}
|
||||
|
||||
pub fn dnd_scroll_gesture_end(&mut self) {
|
||||
|
194
src/niri.rs
194
src/niri.rs
@@ -15,7 +15,7 @@ use anyhow::{bail, ensure, Context};
|
||||
use calloop::futures::Scheduler;
|
||||
use niri_config::{
|
||||
Config, FloatOrInt, Key, Modifiers, OutputName, PreviewRender, TrackLayout,
|
||||
WarpMouseToFocusMode, WorkspaceReference, DEFAULT_BACKGROUND_COLOR,
|
||||
WarpMouseToFocusMode, WorkspaceReference, DEFAULT_BACKDROP_COLOR, DEFAULT_BACKGROUND_COLOR,
|
||||
};
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::input::Keycode;
|
||||
@@ -26,7 +26,8 @@ use smithay::backend::renderer::element::surface::{
|
||||
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
|
||||
};
|
||||
use smithay::backend::renderer::element::utils::{
|
||||
select_dmabuf_feedback, Relocate, RelocateRenderElement,
|
||||
select_dmabuf_feedback, CropRenderElement, Relocate, RelocateRenderElement,
|
||||
RescaleRenderElement,
|
||||
};
|
||||
use smithay::backend::renderer::element::{
|
||||
default_primary_scanout_output_compare, Id, Kind, PrimaryScanoutOutput, RenderElementStates,
|
||||
@@ -131,7 +132,7 @@ use crate::ipc::server::IpcServer;
|
||||
use crate::layer::mapped::LayerSurfaceRenderElement;
|
||||
use crate::layer::MappedLayer;
|
||||
use crate::layout::tile::TileRenderElement;
|
||||
use crate::layout::workspace::WorkspaceId;
|
||||
use crate::layout::workspace::{Workspace, WorkspaceId};
|
||||
use crate::layout::{HitType, Layout, LayoutElement as _, MonitorRenderElement};
|
||||
use crate::niri_render_elements;
|
||||
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
||||
@@ -344,6 +345,7 @@ pub struct Niri {
|
||||
/// Used for limiting the notify to once per iteration, so that it's not spammed with high
|
||||
/// resolution mice.
|
||||
pub notified_activity_this_iteration: bool,
|
||||
pub pointer_inside_hot_corner: bool,
|
||||
pub tablet_cursor_location: Option<Point<f64, Logical>>,
|
||||
pub gesture_swipe_3f_cumulative: Option<(f64, f64)>,
|
||||
pub vertical_wheel_tracker: ScrollTracker,
|
||||
@@ -428,6 +430,7 @@ pub struct OutputState {
|
||||
/// Solid color buffer for the background that we use instead of clearing to avoid damage
|
||||
/// tracking issues and make screenshots easier.
|
||||
pub background_buffer: SolidColorBuffer,
|
||||
pub backdrop_buffer: SolidColorBuffer,
|
||||
pub lock_render_state: LockRenderState,
|
||||
pub lock_surface: Option<LockSurface>,
|
||||
pub lock_color_buffer: SolidColorBuffer,
|
||||
@@ -467,6 +470,7 @@ pub enum KeyboardFocus {
|
||||
LayerShell { surface: WlSurface },
|
||||
LockScreen { surface: Option<WlSurface> },
|
||||
ScreenshotUi,
|
||||
Overview,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, PartialEq)]
|
||||
@@ -563,6 +567,7 @@ impl KeyboardFocus {
|
||||
KeyboardFocus::LayerShell { surface } => Some(surface),
|
||||
KeyboardFocus::LockScreen { surface } => surface.as_ref(),
|
||||
KeyboardFocus::ScreenshotUi => None,
|
||||
KeyboardFocus::Overview => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,12 +577,17 @@ impl KeyboardFocus {
|
||||
KeyboardFocus::LayerShell { surface } => Some(surface),
|
||||
KeyboardFocus::LockScreen { surface } => surface,
|
||||
KeyboardFocus::ScreenshotUi => None,
|
||||
KeyboardFocus::Overview => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_layout(&self) -> bool {
|
||||
matches!(self, KeyboardFocus::Layout { .. })
|
||||
}
|
||||
|
||||
pub fn is_overview(&self) -> bool {
|
||||
matches!(self, KeyboardFocus::Overview)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
@@ -1040,6 +1050,11 @@ impl State {
|
||||
surface = surface.or_else(|| focus_on_layer(Layer::Background));
|
||||
} else {
|
||||
surface = surface.or_else(|| focus_on_layer(Layer::Top));
|
||||
|
||||
if self.niri.layout.is_overview_open() {
|
||||
surface = Some(surface.unwrap_or(KeyboardFocus::Overview));
|
||||
}
|
||||
|
||||
surface = surface.or_else(|| on_d_focus_on_layer(Layer::Bottom));
|
||||
surface = surface.or_else(|| on_d_focus_on_layer(Layer::Background));
|
||||
surface = surface.or_else(layout_focus);
|
||||
@@ -2392,6 +2407,7 @@ impl Niri {
|
||||
pointer_inactivity_timer: None,
|
||||
pointer_inactivity_timer_got_reset: false,
|
||||
notified_activity_this_iteration: false,
|
||||
pointer_inside_hot_corner: false,
|
||||
tablet_cursor_location: None,
|
||||
gesture_swipe_3f_cumulative: None,
|
||||
vertical_wheel_tracker: ScrollTracker::new(120),
|
||||
@@ -2640,6 +2656,9 @@ impl Niri {
|
||||
.to_array_unpremul();
|
||||
background_color[3] = 1.;
|
||||
|
||||
let mut backdrop_color = DEFAULT_BACKDROP_COLOR.to_array_unpremul();
|
||||
backdrop_color[3] = 1.;
|
||||
|
||||
// FIXME: fix winit damage on other transforms.
|
||||
if name.connector == "winit" {
|
||||
transform = Transform::Flipped180;
|
||||
@@ -2673,6 +2692,7 @@ impl Niri {
|
||||
last_drm_sequence: None,
|
||||
frame_callback_sequence: 0,
|
||||
background_buffer: SolidColorBuffer::new(size, background_color),
|
||||
backdrop_buffer: SolidColorBuffer::new(size, backdrop_color),
|
||||
lock_render_state,
|
||||
lock_surface: None,
|
||||
lock_color_buffer: SolidColorBuffer::new(size, CLEAR_COLOR_LOCKED),
|
||||
@@ -2777,6 +2797,7 @@ impl Niri {
|
||||
|
||||
if let Some(state) = self.output_state.get_mut(output) {
|
||||
state.background_buffer.resize(output_size);
|
||||
state.backdrop_buffer.resize(output_size);
|
||||
|
||||
state.lock_color_buffer.resize(output_size);
|
||||
if let Some(lock_surface) = &state.lock_surface {
|
||||
@@ -2876,17 +2897,58 @@ impl Niri {
|
||||
return false;
|
||||
}
|
||||
|
||||
if layer_popup_under(Layer::Top)
|
||||
|| layer_toplevel_under(Layer::Top)
|
||||
|| layer_popup_under(Layer::Bottom)
|
||||
|| layer_popup_under(Layer::Background)
|
||||
{
|
||||
let hot_corner = Rectangle::from_size(Size::from((1., 1.)));
|
||||
if hot_corner.contains(pos_within_output) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if layer_popup_under(Layer::Top) || layer_toplevel_under(Layer::Top) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.layout.is_overview_open() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if layer_popup_under(Layer::Bottom) || layer_popup_under(Layer::Background) {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns the workspace under the position to be activated.
|
||||
///
|
||||
/// The return value is an output and a workspace index on it.
|
||||
pub fn workspace_under(
|
||||
&self,
|
||||
extended_bounds: bool,
|
||||
pos: Point<f64, Logical>,
|
||||
) -> Option<(Output, &Workspace<Mapped>)> {
|
||||
if self.is_locked() || self.screenshot_ui.is_open() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (output, pos_within_output) = self.output_under(pos)?;
|
||||
|
||||
if self.is_layout_obscured_under(output, pos_within_output) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ws = self
|
||||
.layout
|
||||
.workspace_under(extended_bounds, output, pos_within_output)?;
|
||||
Some((output.clone(), ws))
|
||||
}
|
||||
|
||||
pub fn workspace_under_cursor(
|
||||
&self,
|
||||
extended_bounds: bool,
|
||||
) -> Option<(Output, &Workspace<Mapped>)> {
|
||||
let pos = self.seat.get_pointer().unwrap().current_location();
|
||||
self.workspace_under(extended_bounds, pos)
|
||||
}
|
||||
|
||||
/// Returns the window under the position to be activated.
|
||||
///
|
||||
/// The cursor may be inside the window's activation region, but not within the window's input
|
||||
@@ -3017,6 +3079,8 @@ impl Niri {
|
||||
let mut under =
|
||||
layer_popup_under(Layer::Overlay).or_else(|| layer_toplevel_under(Layer::Overlay));
|
||||
|
||||
let is_overview_open = self.layout.is_overview_open();
|
||||
|
||||
// When rendering above the top layer, we put the regular monitor elements first.
|
||||
// Otherwise, we will render all layer-shell pop-ups and the top layer on top.
|
||||
if mon.render_above_top_layer() {
|
||||
@@ -3029,14 +3093,28 @@ impl Niri {
|
||||
.or_else(|| layer_toplevel_under(Layer::Bottom))
|
||||
.or_else(|| layer_toplevel_under(Layer::Background));
|
||||
} else {
|
||||
let hot_corner = Rectangle::from_size(Size::from((1., 1.)));
|
||||
if hot_corner.contains(pos_within_output) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
under = under
|
||||
.or_else(|| layer_popup_under(Layer::Top))
|
||||
.or_else(|| layer_toplevel_under(Layer::Top))
|
||||
.or_else(|| layer_popup_under(Layer::Bottom))
|
||||
.or_else(|| layer_popup_under(Layer::Background))
|
||||
.or_else(window_under)
|
||||
.or_else(|| layer_toplevel_under(Layer::Bottom))
|
||||
.or_else(|| layer_toplevel_under(Layer::Background));
|
||||
.or_else(|| layer_toplevel_under(Layer::Top));
|
||||
|
||||
if !is_overview_open {
|
||||
under = under
|
||||
.or_else(|| layer_popup_under(Layer::Bottom))
|
||||
.or_else(|| layer_popup_under(Layer::Background));
|
||||
}
|
||||
|
||||
under = under.or_else(window_under);
|
||||
|
||||
if !is_overview_open {
|
||||
under = under
|
||||
.or_else(|| layer_toplevel_under(Layer::Bottom))
|
||||
.or_else(|| layer_toplevel_under(Layer::Background));
|
||||
}
|
||||
}
|
||||
|
||||
let Some((mut surface_and_pos, (window, layer))) = under else {
|
||||
@@ -3495,6 +3573,7 @@ impl Niri {
|
||||
// layer-shell, the layout will briefly draw as active, despite never having focus.
|
||||
KeyboardFocus::LockScreen { .. } => true,
|
||||
KeyboardFocus::ScreenshotUi => true,
|
||||
KeyboardFocus::Overview => true,
|
||||
};
|
||||
|
||||
self.layout.refresh(layout_is_active);
|
||||
@@ -3748,10 +3827,18 @@ impl Niri {
|
||||
return elements;
|
||||
}
|
||||
|
||||
// Prepare the background element.
|
||||
// Prepare the background elements.
|
||||
let state = self.output_state.get(output).unwrap();
|
||||
let background_buffer = state.background_buffer.clone();
|
||||
let background = SolidColorRenderElement::from_buffer(
|
||||
&state.background_buffer,
|
||||
&background_buffer,
|
||||
(0, 0),
|
||||
output_scale,
|
||||
1.,
|
||||
Kind::Unspecified,
|
||||
);
|
||||
let backdrop = SolidColorRenderElement::from_buffer(
|
||||
&state.backdrop_buffer,
|
||||
(0, 0),
|
||||
output_scale,
|
||||
1.,
|
||||
@@ -3768,8 +3855,8 @@ impl Niri {
|
||||
.map(OutputRenderElements::from),
|
||||
);
|
||||
|
||||
// Add the background for outputs that were connected while the screenshot UI was open.
|
||||
elements.push(background);
|
||||
// Add the backdrop for outputs that were connected while the screenshot UI was open.
|
||||
elements.push(backdrop);
|
||||
|
||||
if self.debug_draw_opaque_regions {
|
||||
draw_opaque_regions(&mut elements, output_scale);
|
||||
@@ -3788,7 +3875,11 @@ impl Niri {
|
||||
|
||||
// Get monitor elements.
|
||||
let mon = self.layout.monitor_for_output(output).unwrap();
|
||||
let monitor_elements: Vec<_> = mon.render_elements(renderer, target, focus_ring).collect();
|
||||
let ws_scale = mon.workspace_scale();
|
||||
let monitor_elements = Vec::from_iter(
|
||||
mon.render_elements(renderer, target, focus_ring)
|
||||
.map(|(geo, iter)| (geo, Vec::from_iter(iter))),
|
||||
);
|
||||
let int_move_elements: Vec<_> = self
|
||||
.layout
|
||||
.render_interactive_move_for_output(renderer, output, target)
|
||||
@@ -3824,27 +3915,71 @@ impl Niri {
|
||||
.into_iter()
|
||||
.map(OutputRenderElements::from),
|
||||
);
|
||||
elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
|
||||
elements.extend(
|
||||
monitor_elements
|
||||
.into_iter()
|
||||
.flat_map(|(_, iter)| iter)
|
||||
.map(OutputRenderElements::from),
|
||||
);
|
||||
|
||||
elements.extend(top_layer.into_iter().map(OutputRenderElements::from));
|
||||
elements.extend(layer_elems.popups.drain(..).map(OutputRenderElements::from));
|
||||
elements.extend(layer_elems.normal.drain(..).map(OutputRenderElements::from));
|
||||
|
||||
// TODO background
|
||||
} else {
|
||||
elements.extend(top_layer.into_iter().map(OutputRenderElements::from));
|
||||
elements.extend(layer_elems.popups.drain(..).map(OutputRenderElements::from));
|
||||
|
||||
// TODO: adjust input to put interactive move above popups.
|
||||
elements.extend(
|
||||
int_move_elements
|
||||
.into_iter()
|
||||
.map(OutputRenderElements::from),
|
||||
);
|
||||
elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
|
||||
|
||||
elements.extend(layer_elems.normal.drain(..).map(OutputRenderElements::from));
|
||||
for (ws_geo, ws_elements) in monitor_elements {
|
||||
// Collect all other layer-shell elements.
|
||||
let mut layer_elems = SplitElements::default();
|
||||
extend_from_layer(&mut layer_elems, Layer::Bottom);
|
||||
extend_from_layer(&mut layer_elems, Layer::Background);
|
||||
|
||||
let ws_geo = ws_geo.to_physical_precise_round(output_scale);
|
||||
for elem in layer_elems.popups {
|
||||
let elem =
|
||||
RescaleRenderElement::from_element(elem, Point::from((0, 0)), ws_scale);
|
||||
let elem =
|
||||
RelocateRenderElement::from_element(elem, ws_geo.loc, Relocate::Relative);
|
||||
if let Some(elem) = CropRenderElement::from_element(elem, output_scale, ws_geo)
|
||||
{
|
||||
elements.push(OutputRenderElements::from(elem));
|
||||
}
|
||||
}
|
||||
|
||||
elements.extend(ws_elements.into_iter().map(OutputRenderElements::from));
|
||||
|
||||
for elem in layer_elems.normal {
|
||||
let elem =
|
||||
RescaleRenderElement::from_element(elem, Point::from((0, 0)), ws_scale);
|
||||
let elem =
|
||||
RelocateRenderElement::from_element(elem, ws_geo.loc, Relocate::Relative);
|
||||
if let Some(elem) = CropRenderElement::from_element(elem, output_scale, ws_geo)
|
||||
{
|
||||
elements.push(OutputRenderElements::from(elem));
|
||||
}
|
||||
}
|
||||
|
||||
let elem = background.clone();
|
||||
let elem = RescaleRenderElement::from_element(elem, Point::from((0, 0)), ws_scale);
|
||||
let elem =
|
||||
RelocateRenderElement::from_element(elem, ws_geo.loc, Relocate::Relative);
|
||||
if let Some(elem) = CropRenderElement::from_element(elem, output_scale, ws_geo) {
|
||||
elements.push(OutputRenderElements::from(elem));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then the background.
|
||||
elements.push(background);
|
||||
// Then the backdrop.
|
||||
elements.push(backdrop);
|
||||
|
||||
if self.debug_draw_opaque_regions {
|
||||
draw_opaque_regions(&mut elements, output_scale);
|
||||
@@ -5433,7 +5568,7 @@ impl Niri {
|
||||
}
|
||||
|
||||
if let Some(window) = &new_focus.window {
|
||||
if current_focus.window.as_ref() != Some(window) {
|
||||
if !self.layout.is_overview_open() && current_focus.window.as_ref() != Some(window) {
|
||||
let (window, hit) = window;
|
||||
|
||||
// Don't trigger focus-follows-mouse over the tab indicator.
|
||||
@@ -5671,10 +5806,17 @@ niri_render_elements! {
|
||||
OutputRenderElements<R> => {
|
||||
Monitor = MonitorRenderElement<R>,
|
||||
Tile = TileRenderElement<R>,
|
||||
RescaledTile = RescaleRenderElement<TileRenderElement<R>>,
|
||||
LayerSurface = LayerSurfaceRenderElement<R>,
|
||||
RelocatedLayerSurface = CropRenderElement<RelocateRenderElement<RescaleRenderElement<
|
||||
LayerSurfaceRenderElement<R>
|
||||
>>>,
|
||||
Wayland = WaylandSurfaceRenderElement<R>,
|
||||
NamedPointer = MemoryRenderBufferRenderElement<R>,
|
||||
SolidColor = SolidColorRenderElement,
|
||||
RelocatedSolidColor = CropRenderElement<RelocateRenderElement<RescaleRenderElement<
|
||||
SolidColorRenderElement
|
||||
>>>,
|
||||
ScreenshotUi = ScreenshotUiRenderElement,
|
||||
Texture = PrimaryGpuTextureRenderElement,
|
||||
// Used for the CPU-rendered panels.
|
||||
|
@@ -245,6 +245,11 @@ impl ShaderRenderElement {
|
||||
self.area.loc = location;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_alpha(mut self, alpha: f32) -> Self {
|
||||
self.alpha = alpha;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for ShaderRenderElement {
|
||||
|
@@ -175,6 +175,11 @@ impl ShadowRenderElement {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_alpha(mut self, alpha: f32) -> Self {
|
||||
self.inner = self.inner.with_alpha(alpha);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn has_shader(renderer: &mut impl NiriRenderer) -> bool {
|
||||
Shaders::get(renderer)
|
||||
.program(ProgramType::Shadow)
|
||||
|
Reference in New Issue
Block a user