mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Implement tab indicators
This commit is contained in:
@@ -445,6 +445,8 @@ pub struct Layout {
|
||||
#[knuffel(child, default)]
|
||||
pub shadow: Shadow,
|
||||
#[knuffel(child, default)]
|
||||
pub tab_indicator: TabIndicator,
|
||||
#[knuffel(child, default)]
|
||||
pub insert_hint: InsertHint,
|
||||
#[knuffel(child, unwrap(children), default)]
|
||||
pub preset_column_widths: Vec<PresetSize>,
|
||||
@@ -472,6 +474,7 @@ impl Default for Layout {
|
||||
focus_ring: Default::default(),
|
||||
border: Default::default(),
|
||||
shadow: Default::default(),
|
||||
tab_indicator: Default::default(),
|
||||
insert_hint: Default::default(),
|
||||
preset_column_widths: Default::default(),
|
||||
default_column_width: Default::default(),
|
||||
@@ -676,6 +679,49 @@ pub struct ShadowOffset {
|
||||
pub y: FloatOrInt<-65535, 65535>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct TabIndicator {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().gap)]
|
||||
pub gap: FloatOrInt<-65535, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().width)]
|
||||
pub width: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, default = Self::default().length)]
|
||||
pub length: TabIndicatorLength,
|
||||
#[knuffel(child)]
|
||||
pub active_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
pub inactive_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
pub active_gradient: Option<Gradient>,
|
||||
#[knuffel(child)]
|
||||
pub inactive_gradient: Option<Gradient>,
|
||||
}
|
||||
|
||||
impl Default for TabIndicator {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
gap: FloatOrInt(5.),
|
||||
width: FloatOrInt(4.),
|
||||
length: TabIndicatorLength {
|
||||
total_proportion: Some(0.5),
|
||||
},
|
||||
active_color: None,
|
||||
inactive_color: None,
|
||||
active_gradient: None,
|
||||
inactive_gradient: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct TabIndicatorLength {
|
||||
#[knuffel(property)]
|
||||
pub total_proportion: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct InsertHint {
|
||||
#[knuffel(child)]
|
||||
@@ -1101,6 +1147,8 @@ pub struct WindowRule {
|
||||
pub border: BorderRule,
|
||||
#[knuffel(child, default)]
|
||||
pub shadow: ShadowRule,
|
||||
#[knuffel(child, default)]
|
||||
pub tab_indicator: TabIndicatorRule,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub draw_border_with_background: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
@@ -1202,6 +1250,18 @@ pub struct ShadowRule {
|
||||
pub inactive_color: Option<Color>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct TabIndicatorRule {
|
||||
#[knuffel(child)]
|
||||
pub active_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
pub inactive_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
pub active_gradient: Option<Gradient>,
|
||||
#[knuffel(child)]
|
||||
pub inactive_gradient: Option<Gradient>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FloatingPosition {
|
||||
#[knuffel(property)]
|
||||
@@ -2039,6 +2099,23 @@ impl ShadowRule {
|
||||
}
|
||||
}
|
||||
|
||||
impl TabIndicatorRule {
|
||||
pub fn merge_with(&mut self, other: &Self) {
|
||||
if let Some(x) = other.active_color {
|
||||
self.active_color = Some(x);
|
||||
}
|
||||
if let Some(x) = other.inactive_color {
|
||||
self.inactive_color = Some(x);
|
||||
}
|
||||
if let Some(x) = other.active_gradient {
|
||||
self.active_gradient = Some(x);
|
||||
}
|
||||
if let Some(x) = other.inactive_gradient {
|
||||
self.inactive_gradient = Some(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CornerRadius {
|
||||
pub fn fit_to(self, width: f32, height: f32) -> Self {
|
||||
// Like in CSS: https://drafts.csswg.org/css-backgrounds/#corner-overlap
|
||||
@@ -3473,6 +3550,10 @@ mod tests {
|
||||
offset x=10 y=-20
|
||||
}
|
||||
|
||||
tab-indicator {
|
||||
width 10
|
||||
}
|
||||
|
||||
preset-column-widths {
|
||||
proportion 0.25
|
||||
proportion 0.5
|
||||
@@ -3571,6 +3652,10 @@ mod tests {
|
||||
on
|
||||
width 8.5
|
||||
}
|
||||
|
||||
tab-indicator {
|
||||
active-color "#f00"
|
||||
}
|
||||
}
|
||||
|
||||
layer-rule {
|
||||
@@ -3729,6 +3814,10 @@ mod tests {
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
tab_indicator: TabIndicator {
|
||||
width: FloatOrInt(10.),
|
||||
..Default::default()
|
||||
},
|
||||
insert_hint: InsertHint {
|
||||
off: false,
|
||||
color: Color::from_rgba8_unpremul(255, 200, 127, 255),
|
||||
@@ -3875,6 +3964,10 @@ mod tests {
|
||||
width: Some(FloatOrInt(8.5)),
|
||||
..Default::default()
|
||||
},
|
||||
tab_indicator: TabIndicatorRule {
|
||||
active_color: Some(Color::from_rgba8_unpremul(255, 0, 0, 255)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}],
|
||||
layer_rules: vec![
|
||||
|
||||
@@ -262,4 +262,8 @@ impl FocusRing {
|
||||
pub fn is_off(&self) -> bool {
|
||||
self.config.off
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &niri_config::FocusRing {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ pub mod monitor;
|
||||
pub mod opening_window;
|
||||
pub mod scrolling;
|
||||
pub mod shadow;
|
||||
pub mod tab_indicator;
|
||||
pub mod tile;
|
||||
pub mod workspace;
|
||||
|
||||
@@ -312,6 +313,7 @@ pub struct Options {
|
||||
pub focus_ring: niri_config::FocusRing,
|
||||
pub border: niri_config::Border,
|
||||
pub shadow: niri_config::Shadow,
|
||||
pub tab_indicator: niri_config::TabIndicator,
|
||||
pub insert_hint: niri_config::InsertHint,
|
||||
pub center_focused_column: CenterFocusedColumn,
|
||||
pub always_center_single_column: bool,
|
||||
@@ -337,6 +339,7 @@ impl Default for Options {
|
||||
focus_ring: Default::default(),
|
||||
border: Default::default(),
|
||||
shadow: Default::default(),
|
||||
tab_indicator: Default::default(),
|
||||
insert_hint: Default::default(),
|
||||
center_focused_column: Default::default(),
|
||||
always_center_single_column: false,
|
||||
@@ -550,6 +553,7 @@ impl Options {
|
||||
focus_ring: layout.focus_ring,
|
||||
border: layout.border,
|
||||
shadow: layout.shadow,
|
||||
tab_indicator: layout.tab_indicator,
|
||||
insert_hint: layout.insert_hint,
|
||||
center_focused_column: layout.center_focused_column,
|
||||
always_center_single_column: layout.always_center_single_column,
|
||||
|
||||
+67
-10
@@ -11,6 +11,7 @@ use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size};
|
||||
|
||||
use super::closing_window::{ClosingWindow, ClosingWindowRenderElement};
|
||||
use super::insert_hint_element::{InsertHintElement, InsertHintRenderElement};
|
||||
use super::tab_indicator::{TabIndicator, TabIndicatorRenderElement, TabInfo};
|
||||
use super::tile::{Tile, TileRenderElement, TileRenderSnapshot};
|
||||
use super::workspace::{InteractiveResize, ResolvedSize};
|
||||
use super::{ConfigureIntent, InteractiveResizeData, LayoutElement, Options, RemovedTile};
|
||||
@@ -94,6 +95,7 @@ niri_render_elements! {
|
||||
ScrollingSpaceRenderElement<R> => {
|
||||
Tile = TileRenderElement<R>,
|
||||
ClosingWindow = ClosingWindowRenderElement,
|
||||
TabIndicator = TabIndicatorRenderElement,
|
||||
InsertHint = InsertHintRenderElement,
|
||||
}
|
||||
}
|
||||
@@ -174,6 +176,9 @@ pub struct Column<W: LayoutElement> {
|
||||
/// How this column displays and arranges windows.
|
||||
display_mode: ColumnDisplay,
|
||||
|
||||
/// Tab indicator for the tabbed display mode.
|
||||
tab_indicator: TabIndicator,
|
||||
|
||||
/// Animation of the render offset during window swapping.
|
||||
move_animation: Option<Animation>,
|
||||
|
||||
@@ -2527,19 +2532,44 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
}
|
||||
|
||||
let mut first = true;
|
||||
for (tile, tile_pos, visible) in self.tiles_with_render_positions() {
|
||||
// For the active tile (which comes first), draw the focus ring.
|
||||
let focus_ring = focus_ring && first;
|
||||
first = false;
|
||||
|
||||
if !visible {
|
||||
continue;
|
||||
// This matches self.tiles_in_render_order().
|
||||
let view_off = Point::from((-self.view_pos(), 0.));
|
||||
for (col, col_x) in self.columns_in_render_order() {
|
||||
let col_off = Point::from((col_x, 0.));
|
||||
let col_render_off = col.render_offset();
|
||||
|
||||
// Draw the tab indicator on top.
|
||||
{
|
||||
// This is the "static tile position" so to say: it excludes the tile offset (used
|
||||
// for e.g. centering smaller tiles in always-center) and the tile render offset
|
||||
// (used for tile-specific animations).
|
||||
let pos = view_off + col_off + col_render_off + col.tiles_origin();
|
||||
let pos = pos.to_physical_precise_round(scale).to_logical(scale);
|
||||
rv.extend(col.tab_indicator.render(renderer, pos).map(Into::into));
|
||||
}
|
||||
|
||||
rv.extend(
|
||||
tile.render(renderer, tile_pos, scale, focus_ring, target)
|
||||
.map(Into::into),
|
||||
);
|
||||
for (tile, tile_off, visible) in col.tiles_in_render_order() {
|
||||
let tile_pos =
|
||||
view_off + col_off + col_render_off + tile_off + tile.render_offset();
|
||||
// Round to physical pixels.
|
||||
let tile_pos = tile_pos.to_physical_precise_round(scale).to_logical(scale);
|
||||
|
||||
// And now the drawing logic.
|
||||
|
||||
// For the active tile (which comes first), draw the focus ring.
|
||||
let focus_ring = focus_ring && first;
|
||||
first = false;
|
||||
|
||||
if !visible {
|
||||
continue;
|
||||
}
|
||||
|
||||
rv.extend(
|
||||
tile.render(renderer, tile_pos, scale, focus_ring, target)
|
||||
.map(Into::into),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
rv
|
||||
@@ -3268,6 +3298,7 @@ impl<W: LayoutElement> Column<W> {
|
||||
is_full_width,
|
||||
is_fullscreen: false,
|
||||
display_mode,
|
||||
tab_indicator: TabIndicator::new(options.tab_indicator),
|
||||
move_animation: None,
|
||||
view_size,
|
||||
working_area,
|
||||
@@ -3326,6 +3357,7 @@ impl<W: LayoutElement> Column<W> {
|
||||
data.update(tile);
|
||||
}
|
||||
|
||||
self.tab_indicator.update_config(options.tab_indicator);
|
||||
self.view_size = view_size;
|
||||
self.working_area = working_area;
|
||||
self.scale = scale;
|
||||
@@ -3361,6 +3393,31 @@ impl<W: LayoutElement> Column<W> {
|
||||
tile_view_rect.loc -= tile_off + tile.render_offset();
|
||||
tile.update_render_elements(is_active, tile_view_rect);
|
||||
}
|
||||
|
||||
let (tile, tile_off) = self.tiles().nth(self.active_tile_idx).unwrap();
|
||||
let mut tile_view_rect = view_rect;
|
||||
tile_view_rect.loc -= tile_off + tile.render_offset();
|
||||
|
||||
let config = self.tab_indicator.config();
|
||||
let tabs = self.tiles.iter().enumerate().map(|(tile_idx, tile)| {
|
||||
let is_active = tile_idx == active_idx;
|
||||
TabInfo::from_tile(tile, is_active, &config)
|
||||
});
|
||||
|
||||
// Hide the tab indicator in fullscreen. If you have it configured to overlap the window,
|
||||
// you don't want that to happen in fullscreen. Also, laying things out correctly when the
|
||||
// tab indicator is within the column and the column goes fullscreen, would require too
|
||||
// many changes to the code for too little benefit (it's mostly invisible anyway).
|
||||
let enabled = self.display_mode == ColumnDisplay::Tabbed && !self.is_fullscreen;
|
||||
|
||||
self.tab_indicator.update_render_elements(
|
||||
enabled,
|
||||
tile.animated_tile_size(),
|
||||
tile_view_rect,
|
||||
tabs,
|
||||
is_active,
|
||||
self.scale,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn render_offset(&self) -> Point<f64, Logical> {
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
use std::iter::zip;
|
||||
|
||||
use niri_config::{CornerRadius, Gradient, GradientRelativeTo};
|
||||
use smithay::utils::{Logical, Point, Rectangle, Size};
|
||||
|
||||
use super::tile::Tile;
|
||||
use super::LayoutElement;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::border::BorderRenderElement;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::utils::{floor_logical_in_physical_max1, round_logical_in_physical};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TabIndicator {
|
||||
shader_locs: Vec<Point<f64, Logical>>,
|
||||
shaders: Vec<BorderRenderElement>,
|
||||
config: niri_config::TabIndicator,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TabInfo {
|
||||
pub gradient: Gradient,
|
||||
}
|
||||
|
||||
niri_render_elements! {
|
||||
TabIndicatorRenderElement => {
|
||||
Gradient = BorderRenderElement,
|
||||
}
|
||||
}
|
||||
|
||||
impl TabIndicator {
|
||||
pub fn new(config: niri_config::TabIndicator) -> Self {
|
||||
Self {
|
||||
shader_locs: Vec::new(),
|
||||
shaders: Vec::new(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_config(&mut self, config: niri_config::TabIndicator) {
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
pub fn update_shaders(&mut self) {
|
||||
for elem in &mut self.shaders {
|
||||
elem.damage_all();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_render_elements(
|
||||
&mut self,
|
||||
enabled: bool,
|
||||
tile_size: Size<f64, Logical>,
|
||||
tile_view_rect: Rectangle<f64, Logical>,
|
||||
tabs: impl Iterator<Item = TabInfo> + Clone,
|
||||
// TODO: do we indicate inactive-but-selected somehow?
|
||||
_is_active: bool,
|
||||
scale: f64,
|
||||
) {
|
||||
if !enabled || self.config.off {
|
||||
self.shader_locs.clear();
|
||||
self.shaders.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Tab indicators are rendered relative to the tile geometry.
|
||||
let tile_geo = Rectangle::new(Point::from((0., 0.)), tile_size);
|
||||
|
||||
let round = |logical: f64| round_logical_in_physical(scale, logical);
|
||||
|
||||
let width = round(self.config.width.0);
|
||||
let gap = round(self.config.gap.0);
|
||||
|
||||
let total_prop = self.config.length.total_proportion.unwrap_or(0.5);
|
||||
let min_length = round(tile_size.h * total_prop.clamp(0., 2.));
|
||||
|
||||
let count = tabs.clone().count();
|
||||
self.shaders.resize_with(count, Default::default);
|
||||
self.shader_locs.resize_with(count, Default::default);
|
||||
|
||||
let pixel = 1. / scale;
|
||||
let shortest_length = count as f64 * pixel;
|
||||
let length = f64::max(min_length, shortest_length);
|
||||
let px_per_tab = length / count as f64;
|
||||
let px_per_tab = floor_logical_in_physical_max1(scale, px_per_tab);
|
||||
let floored_length = count as f64 * px_per_tab;
|
||||
let mut ones_left = ((length - floored_length) / pixel).max(0.).round() as usize;
|
||||
|
||||
let mut shader_loc = Point::from((-gap - width, round((tile_size.h - length) / 2.)));
|
||||
|
||||
for ((shader, loc), tab) in zip(&mut self.shaders, &mut self.shader_locs).zip(tabs) {
|
||||
*loc = shader_loc;
|
||||
|
||||
let mut px_per_tab = px_per_tab;
|
||||
if ones_left > 0 {
|
||||
ones_left -= 1;
|
||||
px_per_tab += pixel;
|
||||
}
|
||||
shader_loc.y += px_per_tab;
|
||||
|
||||
let shader_size = Size::from((width, px_per_tab));
|
||||
|
||||
let mut gradient_area = match tab.gradient.relative_to {
|
||||
GradientRelativeTo::Window => tile_geo,
|
||||
GradientRelativeTo::WorkspaceView => tile_view_rect,
|
||||
};
|
||||
gradient_area.loc -= *loc;
|
||||
|
||||
shader.update(
|
||||
shader_size,
|
||||
gradient_area,
|
||||
tab.gradient.in_,
|
||||
tab.gradient.from,
|
||||
tab.gradient.to,
|
||||
((tab.gradient.angle as f32) - 90.).to_radians(),
|
||||
Rectangle::from_size(shader_size),
|
||||
0.,
|
||||
CornerRadius::default(),
|
||||
scale as f32,
|
||||
1.,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
renderer: &mut impl NiriRenderer,
|
||||
tile_pos: Point<f64, Logical>,
|
||||
) -> impl Iterator<Item = TabIndicatorRenderElement> + '_ {
|
||||
let has_border_shader = BorderRenderElement::has_shader(renderer);
|
||||
if !has_border_shader {
|
||||
return None.into_iter().flatten();
|
||||
}
|
||||
|
||||
let rv = zip(&self.shaders, &self.shader_locs)
|
||||
.map(move |(shader, loc)| shader.clone().with_location(tile_pos + *loc))
|
||||
.map(TabIndicatorRenderElement::from);
|
||||
|
||||
Some(rv).into_iter().flatten()
|
||||
}
|
||||
|
||||
pub fn config(&self) -> niri_config::TabIndicator {
|
||||
self.config
|
||||
}
|
||||
}
|
||||
|
||||
impl TabInfo {
|
||||
pub fn from_tile<W: LayoutElement>(
|
||||
tile: &Tile<W>,
|
||||
is_active: bool,
|
||||
config: &niri_config::TabIndicator,
|
||||
) -> Self {
|
||||
let rules = tile.window().rules();
|
||||
let rule = rules.tab_indicator;
|
||||
|
||||
let gradient_from_rule = || {
|
||||
let (color, gradient) = if is_active {
|
||||
(rule.active_color, rule.active_gradient)
|
||||
} else {
|
||||
(rule.inactive_color, rule.inactive_gradient)
|
||||
};
|
||||
let color = color.map(Gradient::from);
|
||||
gradient.or(color)
|
||||
};
|
||||
|
||||
let gradient_from_config = || {
|
||||
let (color, gradient) = if is_active {
|
||||
(config.active_color, config.active_gradient)
|
||||
} else {
|
||||
(config.inactive_color, config.inactive_gradient)
|
||||
};
|
||||
let color = color.map(Gradient::from);
|
||||
gradient.or(color)
|
||||
};
|
||||
|
||||
let gradient_from_border = || {
|
||||
// Come up with tab indicator gradient matching the focus ring or the border, whichever
|
||||
// one is enabled.
|
||||
let focus_ring_config = tile.focus_ring().config();
|
||||
let border_config = tile.border().config();
|
||||
let config = if focus_ring_config.off {
|
||||
border_config
|
||||
} else {
|
||||
focus_ring_config
|
||||
};
|
||||
|
||||
let (color, gradient) = if is_active {
|
||||
(config.active_color, config.active_gradient)
|
||||
} else {
|
||||
(config.inactive_color, config.inactive_gradient)
|
||||
};
|
||||
gradient.unwrap_or_else(|| Gradient::from(color))
|
||||
};
|
||||
|
||||
let gradient = gradient_from_rule()
|
||||
.or_else(gradient_from_config)
|
||||
.unwrap_or_else(gradient_from_border);
|
||||
|
||||
TabInfo { gradient }
|
||||
}
|
||||
}
|
||||
+20
-1
@@ -1,6 +1,6 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use niri_config::{FloatOrInt, OutputName, WorkspaceName, WorkspaceReference};
|
||||
use niri_config::{FloatOrInt, OutputName, TabIndicatorLength, WorkspaceName, WorkspaceReference};
|
||||
use proptest::prelude::*;
|
||||
use proptest_derive::Arbitrary;
|
||||
use smithay::output::{Mode, PhysicalProperties, Subpixel};
|
||||
@@ -3239,6 +3239,23 @@ prop_compose! {
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arbitrary_tab_indicator()(
|
||||
off in any::<bool>(),
|
||||
width in arbitrary_spacing(),
|
||||
gap in arbitrary_spacing_neg(),
|
||||
length in (0f64..2f64),
|
||||
) -> niri_config::TabIndicator {
|
||||
niri_config::TabIndicator {
|
||||
off,
|
||||
width: FloatOrInt(width),
|
||||
gap: FloatOrInt(gap),
|
||||
length: TabIndicatorLength { total_proportion: Some(length) },
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arbitrary_options()(
|
||||
gaps in arbitrary_spacing(),
|
||||
@@ -3246,6 +3263,7 @@ prop_compose! {
|
||||
focus_ring in arbitrary_focus_ring(),
|
||||
border in arbitrary_border(),
|
||||
shadow in arbitrary_shadow(),
|
||||
tab_indicator in arbitrary_tab_indicator(),
|
||||
center_focused_column in arbitrary_center_focused_column(),
|
||||
always_center_single_column in any::<bool>(),
|
||||
empty_workspace_above_first in any::<bool>(),
|
||||
@@ -3259,6 +3277,7 @@ prop_compose! {
|
||||
focus_ring,
|
||||
border,
|
||||
shadow,
|
||||
tab_indicator,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -563,7 +563,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
size
|
||||
}
|
||||
|
||||
fn animated_window_size(&self) -> Size<f64, Logical> {
|
||||
pub fn animated_window_size(&self) -> Size<f64, Logical> {
|
||||
let mut size = self.window_size();
|
||||
|
||||
if let Some(resize) = &self.resize_animation {
|
||||
@@ -580,7 +580,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
size
|
||||
}
|
||||
|
||||
fn animated_tile_size(&self) -> Size<f64, Logical> {
|
||||
pub fn animated_tile_size(&self) -> Size<f64, Logical> {
|
||||
let mut size = self.animated_window_size();
|
||||
|
||||
if self.is_fullscreen {
|
||||
@@ -1031,6 +1031,14 @@ impl<W: LayoutElement> Tile<W> {
|
||||
self.unmap_snapshot.take()
|
||||
}
|
||||
|
||||
pub fn border(&self) -> &FocusRing {
|
||||
&self.border
|
||||
}
|
||||
|
||||
pub fn focus_ring(&self) -> &FocusRing {
|
||||
&self.focus_ring
|
||||
}
|
||||
|
||||
pub fn options(&self) -> &Rc<Options> {
|
||||
&self.options
|
||||
}
|
||||
|
||||
@@ -127,6 +127,14 @@ pub fn round_logical_in_physical_max1(scale: f64, logical: f64) -> f64 {
|
||||
(logical * scale).max(1.).round() / scale
|
||||
}
|
||||
|
||||
pub fn floor_logical_in_physical_max1(scale: f64, logical: f64) -> f64 {
|
||||
if logical == 0. {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
(logical * scale).max(1.).floor() / scale
|
||||
}
|
||||
|
||||
pub fn output_size(output: &Output) -> Size<f64, Logical> {
|
||||
let output_scale = output.current_scale().fractional_scale();
|
||||
let output_transform = output.current_transform();
|
||||
|
||||
+10
-1
@@ -2,7 +2,7 @@ use std::cmp::{max, min};
|
||||
|
||||
use niri_config::{
|
||||
BlockOutFrom, BorderRule, CornerRadius, FloatingPosition, Match, PresetSize, ShadowRule,
|
||||
WindowRule,
|
||||
TabIndicatorRule, WindowRule,
|
||||
};
|
||||
use niri_ipc::ColumnDisplay;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
@@ -83,6 +83,8 @@ pub struct ResolvedWindowRules {
|
||||
pub border: BorderRule,
|
||||
/// Shadow overrides.
|
||||
pub shadow: ShadowRule,
|
||||
/// Tab indicator overrides.
|
||||
pub tab_indicator: TabIndicatorRule,
|
||||
|
||||
/// Whether or not to draw the border with a solid background.
|
||||
///
|
||||
@@ -191,6 +193,12 @@ impl ResolvedWindowRules {
|
||||
color: None,
|
||||
inactive_color: None,
|
||||
},
|
||||
tab_indicator: TabIndicatorRule {
|
||||
active_color: None,
|
||||
inactive_color: None,
|
||||
active_gradient: None,
|
||||
inactive_gradient: None,
|
||||
},
|
||||
draw_border_with_background: None,
|
||||
opacity: None,
|
||||
geometry_corner_radius: None,
|
||||
@@ -290,6 +298,7 @@ impl ResolvedWindowRules {
|
||||
resolved.focus_ring.merge_with(&rule.focus_ring);
|
||||
resolved.border.merge_with(&rule.border);
|
||||
resolved.shadow.merge_with(&rule.shadow);
|
||||
resolved.tab_indicator.merge_with(&rule.tab_indicator);
|
||||
|
||||
if let Some(x) = rule.draw_border_with_background {
|
||||
resolved.draw_border_with_background = Some(x);
|
||||
|
||||
Reference in New Issue
Block a user