diff options
| -rw-r--r-- | niri-config/src/lib.rs | 12 | ||||
| -rw-r--r-- | niri-visual-tests/src/cases/layout.rs | 1 | ||||
| -rw-r--r-- | src/layout/mod.rs | 2 | ||||
| -rw-r--r-- | src/layout/monitor.rs | 147 | ||||
| -rw-r--r-- | src/niri.rs | 156 |
5 files changed, 232 insertions, 86 deletions
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index d622bfcd..34a3b6fa 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -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; @@ -444,6 +445,8 @@ pub struct Output { pub focus_at_startup: bool, #[knuffel(child, default = DEFAULT_BACKGROUND_COLOR)] pub background_color: Color, + #[knuffel(child, default = DEFAULT_BACKDROP_COLOR)] + pub backdrop_color: Color, } impl Output { @@ -472,6 +475,7 @@ impl Default for Output { mode: None, variable_refresh_rate: None, background_color: DEFAULT_BACKGROUND_COLOR, + backdrop_color: DEFAULT_BACKDROP_COLOR, } } } @@ -4127,6 +4131,12 @@ mod tests { b: 0.4, a: 1.0, }, + backdrop_color: Color { + r: 0.15, + g: 0.15, + b: 0.15, + a: 1.0, + }, }, ], ), diff --git a/niri-visual-tests/src/cases/layout.rs b/niri-visual-tests/src/cases/layout.rs index 23f2bc8d..81748e8c 100644 --- a/niri-visual-tests/src/cases/layout.rs +++ b/niri-visual-tests/src/cases/layout.rs @@ -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() } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 040a5cb4..cdb96113 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -2279,7 +2279,7 @@ impl<W: LayoutElement> Layout<W> { ) -> Option<(&W, HitType)> { if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { if move_.output == *output { - let tile_pos = move_.tile_render_location(1.); + let tile_pos = move_.tile_render_location(); HitType::hit_tile(&move_.tile, tile_pos, pos_within_output) } else { None diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index 4c20d533..ab5384a8 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -25,7 +25,9 @@ use crate::render_helpers::renderer::NiriRenderer; 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, round_logical_in_physical_max1, ResizeEdge, +}; /// Amount of touchpad movement to scroll the height of one workspace. const WORKSPACE_GESTURE_MOVEMENT: f64 = 300.; @@ -943,6 +945,23 @@ impl<W: LayoutElement> Monitor<W> { self.active_workspace_ref().active_tile_visual_rectangle() } + fn workspace_size(&self, zoom: f64) -> Size<f64, Logical> { + let ws_size = self.view_size.upscale(zoom); + let scale = self.scale.fractional_scale(); + ws_size.to_physical_precise_ceil(scale).to_logical(scale) + } + + fn workspace_gap(&self, zoom: f64) -> f64 { + let scale = self.scale.fractional_scale(); + let gap = self.view_size.h * 0.1 * zoom; + round_logical_in_physical_max1(scale, gap) + } + + fn workspace_size_with_gap(&self, zoom: f64) -> Size<f64, Logical> { + let gap = self.workspace_gap(zoom); + self.workspace_size(zoom) + Size::from((0., gap)) + } + pub fn workspace_render_idx(&self) -> f64 { if let Some(switch) = &self.workspace_switch { switch.current_idx() @@ -953,17 +972,19 @@ impl<W: LayoutElement> Monitor<W> { pub fn workspaces_render_geo(&self) -> impl Iterator<Item = Rectangle<f64, Logical>> { let scale = self.scale.fractional_scale(); - let size = self.view_size; + let zoom = 1.; - // Ceil the workspace size in physical pixels. - let ws_size = size.to_physical_precise_ceil(scale).to_logical(scale); + let ws_size = self.workspace_size(zoom); + let gap = self.workspace_gap(zoom); + let ws_height_with_gap = ws_size.h + gap; - let first_ws_y = -self.workspace_render_idx() * ws_size.h; + let first_ws_y = -self.workspace_render_idx() * ws_height_with_gap; + let first_ws_y = round_logical_in_physical(scale, first_ws_y); - (0..self.workspaces.len()).map(move |idx| { - let y = first_ws_y + idx as f64 * ws_size.h; + // Return position for one-past-last workspace too. + (0..=self.workspaces.len()).map(move |idx| { + let y = first_ws_y + idx as f64 * ws_height_with_gap; let loc = Point::from((0., y)); - let loc = loc.to_physical_precise_round(scale).to_logical(scale); Rectangle::new(loc, ws_size) }) } @@ -1031,7 +1052,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.scale.fractional_scale(); @@ -1072,45 +1098,45 @@ impl<W: LayoutElement> Monitor<W> { } } - self.workspaces_with_render_geo() - .flat_map(move |(ws, geo)| { - let map_ws_contents = move |elem: WorkspaceRenderElement<R>| { + self.workspaces_with_render_geo().map(move |(ws, geo)| { + let map_ws_contents = move |elem: WorkspaceRenderElement<R>| { + let elem = CropRenderElement::from_element(elem, scale, crop_bounds)?; + let elem = MonitorInnerRenderElement::Workspace(elem); + Some(elem) + }; + + let (floating, scrolling) = ws.render_elements(renderer, target, focus_ring); + let floating = floating.filter_map(map_ws_contents); + let scrolling = scrolling.filter_map(map_ws_contents); + + let hint = if matches!(insert_hint, Some((hint_ws_id, _)) if hint_ws_id == ws.id()) { + let iter = insert_hint.take().unwrap().1; + let iter = iter.filter_map(move |elem| { let elem = CropRenderElement::from_element(elem, scale, crop_bounds)?; - let elem = MonitorInnerRenderElement::Workspace(elem); + let elem = MonitorInnerRenderElement::InsertHint(elem); Some(elem) - }; - - let (floating, scrolling) = ws.render_elements(renderer, target, focus_ring); - let floating = floating.filter_map(map_ws_contents); - let scrolling = scrolling.filter_map(map_ws_contents); - - let hint = if matches!(insert_hint, Some((hint_ws_id, _)) if hint_ws_id == ws.id()) - { - let iter = insert_hint.take().unwrap().1; - let iter = iter.filter_map(move |elem| { - let elem = CropRenderElement::from_element(elem, scale, crop_bounds)?; - let elem = MonitorInnerRenderElement::InsertHint(elem); - Some(elem) - }); - Some(iter) - } else { - None - }; - let hint = hint.into_iter().flatten(); - - let iter = floating.chain(hint).chain(scrolling); - - iter.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, - ) - }) - }) + }); + Some(iter) + } else { + None + }; + let hint = hint.into_iter().flatten(); + + let iter = floating.chain(hint).chain(scrolling); + + let iter = iter.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, + ) + }); + + (geo, iter) + }) } pub fn workspace_switch_gesture_begin(&mut self, is_touchpad: bool) { @@ -1133,7 +1159,7 @@ impl<W: LayoutElement> Monitor<W> { timestamp: Duration, is_touchpad: bool, ) -> Option<bool> { - let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else { + let Some(WorkspaceSwitch::Gesture(gesture)) = &self.workspace_switch else { return None; }; @@ -1141,13 +1167,18 @@ impl<W: LayoutElement> Monitor<W> { return None; } - gesture.tracker.push(delta_y, timestamp); - let total_height = if gesture.is_touchpad { WORKSPACE_GESTURE_MOVEMENT } else { - self.workspaces[0].view_size().h + self.workspace_size_with_gap(1.).h + }; + + let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else { + return None; }; + + gesture.tracker.push(delta_y, timestamp); + let pos = gesture.tracker.pos() / total_height; let (min, max) = gesture.min_max(self.workspaces.len()); @@ -1163,7 +1194,7 @@ impl<W: LayoutElement> Monitor<W> { } pub fn workspace_switch_gesture_end(&mut self, is_touchpad: Option<bool>) -> bool { - let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else { + let Some(WorkspaceSwitch::Gesture(gesture)) = &self.workspace_switch else { return false; }; @@ -1171,16 +1202,20 @@ impl<W: LayoutElement> Monitor<W> { return false; } - // Take into account any idle time between the last event and now. - let now = self.clock.now_unadjusted(); - gesture.tracker.push(0., now); - let total_height = if gesture.is_touchpad { WORKSPACE_GESTURE_MOVEMENT } else { - self.workspaces[0].view_size().h + self.workspace_size_with_gap(1.).h + }; + + let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else { + return false; }; + // Take into account any idle time between the last event and now. + let now = self.clock.now_unadjusted(); + gesture.tracker.push(0., now); + 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; diff --git a/src/niri.rs b/src/niri.rs index 2543ddb3..de88da71 100644 --- a/src/niri.rs +++ b/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,10 +26,12 @@ 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, + default_primary_scanout_output_compare, Element, Id, Kind, PrimaryScanoutOutput, + RenderElementStates, }; use smithay::backend::renderer::gles::GlesRenderer; use smithay::backend::renderer::sync::SyncPoint; @@ -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, @@ -1465,11 +1468,22 @@ impl State { background_color[3] = 1.; let background_color = Color32F::from(background_color); + let mut backdrop_color = config + .map(|c| c.backdrop_color) + .unwrap_or(DEFAULT_BACKDROP_COLOR) + .to_array_unpremul(); + backdrop_color[3] = 1.; + let backdrop_color = Color32F::from(backdrop_color); + if let Some(state) = self.niri.output_state.get_mut(output) { if state.background_buffer.color() != background_color { state.background_buffer.set_color(background_color); recolored_outputs.push(output.clone()); } + if state.backdrop_buffer.color() != backdrop_color { + state.backdrop_buffer.set_color(backdrop_color); + recolored_outputs.push(output.clone()); + } } } @@ -2640,6 +2654,12 @@ impl Niri { .to_array_unpremul(); background_color[3] = 1.; + let mut backdrop_color = c + .map(|c| c.backdrop_color) + .unwrap_or(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 +2693,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 +2798,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 { @@ -2894,11 +2916,18 @@ impl Niri { layers .layers_on(layer) .rev() - .find_map(|layer| { - let layer_pos_within_output = - layers.layer_geometry(layer).unwrap().loc.to_f64(); + .find_map(|layer_surface| { + let mut layer_pos_within_output = + layers.layer_geometry(layer_surface).unwrap().loc.to_f64(); + + // Background and bottom layers move together with the workspaces. + let mon = self.layout.monitor_for_output(output)?; + let (_, geo) = mon.workspace_under(pos_within_output)?; + layer_pos_within_output += geo.loc; + let surface_type = WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE; - layer.surface_under(pos_within_output - layer_pos_within_output, surface_type) + layer_surface + .surface_under(pos_within_output - layer_pos_within_output, surface_type) }) .is_some() }; @@ -3001,20 +3030,29 @@ impl Niri { layers .layers_on(layer) .rev() - .find_map(|layer| { - let layer_pos_within_output = - layers.layer_geometry(layer).unwrap().loc.to_f64(); + .find_map(|layer_surface| { + let mut layer_pos_within_output = + layers.layer_geometry(layer_surface).unwrap().loc.to_f64(); + + // Background and bottom layers move together with the workspaces. + if matches!(layer, Layer::Background | Layer::Bottom) { + let mon = self.layout.monitor_for_output(output)?; + let (_, geo) = mon.workspace_under(pos_within_output)?; + layer_pos_within_output += geo.loc; + } + let surface_type = if popup { WindowSurfaceType::POPUP } else { WindowSurfaceType::TOPLEVEL } | WindowSurfaceType::SUBSURFACE; - layer + + layer_surface .surface_under(pos_within_output - layer_pos_within_output, surface_type) .map(|(surface, pos_within_layer)| { ( (surface, pos_within_layer.to_f64() + layer_pos_within_output), - layer, + layer_surface, ) }) }) @@ -3791,10 +3829,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., @@ -3811,8 +3857,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); @@ -3831,7 +3877,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 zoom = 1.; + 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) @@ -3854,24 +3904,31 @@ impl Niri { extend_from_layer(&mut layer_elems, Layer::Top); let top_layer = layer_elems; - // 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); - // 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() { + // 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); + elements.extend( int_move_elements .into_iter() .map(OutputRenderElements::from), ); - elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from)); + elements.extend( + monitor_elements + .into_iter() + .flat_map(|(_ws_geo, 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)); + + elements.push(OutputRenderElements::from(background)); } else { elements.extend(top_layer.into_iter().map(OutputRenderElements::from)); @@ -3881,15 +3938,40 @@ impl Niri { .map(OutputRenderElements::from), ); - elements.extend(layer_elems.popups.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); + + elements.extend( + layer_elems + .popups + .into_iter() + .filter_map(|elem| scale_relocate_crop(elem, output_scale, zoom, ws_geo)) + .map(OutputRenderElements::from), + ); - elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from)); + elements.extend(ws_elements.into_iter().map(OutputRenderElements::from)); - elements.extend(layer_elems.normal.drain(..).map(OutputRenderElements::from)); + elements.extend( + layer_elems + .normal + .into_iter() + .filter_map(|elem| scale_relocate_crop(elem, output_scale, zoom, ws_geo)) + .map(OutputRenderElements::from), + ); + + if let Some(elem) = + scale_relocate_crop(background.clone(), output_scale, zoom, 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); @@ -5712,14 +5794,32 @@ impl ClientData for ClientState { fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {} } +fn scale_relocate_crop<E: Element>( + elem: E, + output_scale: Scale<f64>, + zoom: f64, + ws_geo: Rectangle<f64, Logical>, +) -> Option<CropRenderElement<RelocateRenderElement<RescaleRenderElement<E>>>> { + let ws_geo = ws_geo.to_physical_precise_round(output_scale); + let elem = RescaleRenderElement::from_element(elem, Point::from((0, 0)), zoom); + let elem = RelocateRenderElement::from_element(elem, ws_geo.loc, Relocate::Relative); + CropRenderElement::from_element(elem, output_scale, ws_geo) +} + niri_render_elements! { OutputRenderElements<R> => { Monitor = MonitorRenderElement<R>, Tile = 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. |
