tabs: hy3 tab styling, with corresponding config options.
tab-bar-height = 22 tab-bar-padding = 5 tab-bar-radius = 6 tab-bar-border-width = 2 tab-bar-text-padding = 3 tab-bar-gap = 6 tab-title-align = "center" tab-active-bg-color = "#33ccff40" tab-active-border-color = "#33ccffee" tab-active-text-color = "#ffffffff" tab-focused-bg-color = "#60606040" tab-focused-border-color = "#808080ee" tab-focused-text-color = "#ffffffff" tab-inactive-bg-color = "#30303020" tab-inactive-border-color = "#606060aa" tab-inactive-text-color = "#ffffffff" tab-urgent-bg-color = "#ff223340" tab-urgent-border-color = "#ff2233ee" tab-urgent-text-color = "#ffffffff"
This commit is contained in:
parent
e35dce433a
commit
e8f86dae8a
28 changed files with 920 additions and 242 deletions
|
|
@ -2055,6 +2055,7 @@ impl ConfigClient {
|
||||||
radius
|
radius
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn seat_toggle_expand(&self, seat: Seat) {
|
pub fn seat_toggle_expand(&self, seat: Seat) {
|
||||||
self.send(&ClientMessage::SeatToggleExpand { seat });
|
self.send(&ClientMessage::SeatToggleExpand { seat });
|
||||||
}
|
}
|
||||||
|
|
@ -2087,6 +2088,14 @@ impl ConfigClient {
|
||||||
self.send(&ClientMessage::SetTabTitleAlign { align });
|
self.send(&ClientMessage::SetTabTitleAlign { align });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_tab_from_top(&self, from_top: bool) {
|
||||||
|
self.send(&ClientMessage::SetTabFromTop { from_top });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_tab_render_text(&self, render: bool) {
|
||||||
|
self.send(&ClientMessage::SetTabRenderText { render });
|
||||||
|
}
|
||||||
|
|
||||||
pub fn seat_move_tab(&self, seat: Seat, right: bool) {
|
pub fn seat_move_tab(&self, seat: Seat, right: bool) {
|
||||||
self.send(&ClientMessage::SeatMoveTab { seat, right });
|
self.send(&ClientMessage::SeatMoveTab { seat, right });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -911,6 +911,12 @@ pub enum ClientMessage<'a> {
|
||||||
SetTabTitleAlign {
|
SetTabTitleAlign {
|
||||||
align: u32,
|
align: u32,
|
||||||
},
|
},
|
||||||
|
SetTabFromTop {
|
||||||
|
from_top: bool,
|
||||||
|
},
|
||||||
|
SetTabRenderText {
|
||||||
|
render: bool,
|
||||||
|
},
|
||||||
SeatMoveTab {
|
SeatMoveTab {
|
||||||
seat: Seat,
|
seat: Seat,
|
||||||
right: bool,
|
right: bool,
|
||||||
|
|
|
||||||
|
|
@ -464,6 +464,21 @@ pub fn set_tab_title_align(align: &str) {
|
||||||
get!().set_tab_title_align(val)
|
get!().set_tab_title_align(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether tab enter/exit animations slide from the top.
|
||||||
|
///
|
||||||
|
/// When `true`, tabs slide in from above and out upwards.
|
||||||
|
/// When `false` (default), tabs slide in from below and out downwards.
|
||||||
|
pub fn set_tab_from_top(from_top: bool) {
|
||||||
|
get!().set_tab_from_top(from_top)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether text is rendered on tab buttons.
|
||||||
|
///
|
||||||
|
/// The default is `true`.
|
||||||
|
pub fn set_tab_render_text(render: bool) {
|
||||||
|
get!().set_tab_render_text(render)
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets a callback to run when this config is unloaded.
|
/// Sets a callback to run when this config is unloaded.
|
||||||
///
|
///
|
||||||
/// Only one callback can be set at a time. If another callback is already set, it will be
|
/// Only one callback can be set at a time. If another callback is already set, it will be
|
||||||
|
|
|
||||||
|
|
@ -334,6 +334,30 @@ pub mod colors {
|
||||||
///
|
///
|
||||||
/// Default: `#23092c`.
|
/// Default: `#23092c`.
|
||||||
const 23 => TAB_ATTENTION_BACKGROUND_COLOR,
|
const 23 => TAB_ATTENTION_BACKGROUND_COLOR,
|
||||||
|
/// The background color of a focused (keyboard-focused) tab.
|
||||||
|
///
|
||||||
|
/// Default: `#3a5a70`.
|
||||||
|
const 24 => TAB_FOCUSED_BACKGROUND_COLOR,
|
||||||
|
/// The border color of a focused (keyboard-focused) tab.
|
||||||
|
///
|
||||||
|
/// Default: `#285577`.
|
||||||
|
const 25 => TAB_FOCUSED_BORDER_COLOR,
|
||||||
|
/// The text color of a focused (keyboard-focused) tab.
|
||||||
|
///
|
||||||
|
/// Default: `#dddddd`.
|
||||||
|
const 26 => TAB_FOCUSED_TEXT_COLOR,
|
||||||
|
/// The background color of an urgent tab.
|
||||||
|
///
|
||||||
|
/// Default: `#23092c`.
|
||||||
|
const 27 => TAB_URGENT_BACKGROUND_COLOR,
|
||||||
|
/// The border color of an urgent tab.
|
||||||
|
///
|
||||||
|
/// Default: `#441155`.
|
||||||
|
const 28 => TAB_URGENT_BORDER_COLOR,
|
||||||
|
/// The text color of an urgent tab.
|
||||||
|
///
|
||||||
|
/// Default: `#ffffff`.
|
||||||
|
const 29 => TAB_URGENT_TEXT_COLOR,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the color of GUI element.
|
/// Sets the color of GUI element.
|
||||||
|
|
@ -430,5 +454,9 @@ pub mod sized {
|
||||||
///
|
///
|
||||||
/// Default: 4
|
/// Default: 4
|
||||||
const 12 => TAB_BAR_GAP,
|
const 12 => TAB_BAR_GAP,
|
||||||
|
/// The opacity of tabs in the tab bar (0-100).
|
||||||
|
///
|
||||||
|
/// Default: 100
|
||||||
|
const 13 => TAB_OPACITY,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -399,10 +399,10 @@ fn start_compositor2(
|
||||||
hyprland_global_shortcuts: Default::default(),
|
hyprland_global_shortcuts: Default::default(),
|
||||||
layer_rules: Default::default(),
|
layer_rules: Default::default(),
|
||||||
blur_config: Default::default(),
|
blur_config: Default::default(),
|
||||||
blur_cache_epoch: Default::default(),
|
|
||||||
animations_config: Default::default(),
|
animations_config: Default::default(),
|
||||||
active_animations: Default::default(),
|
active_animations: Default::default(),
|
||||||
close_snapshots: Default::default(),
|
close_snapshots: Default::default(),
|
||||||
|
tab_animation_containers: Default::default(),
|
||||||
});
|
});
|
||||||
state.tracker.register(ClientId::from_raw(0));
|
state.tracker.register(ClientId::from_raw(0));
|
||||||
create_dummy_output(&state);
|
create_dummy_output(&state);
|
||||||
|
|
|
||||||
|
|
@ -2505,6 +2505,7 @@ impl ConfigProxyHandler {
|
||||||
TAB_BAR_BORDER_WIDTH => ThemeSized::tab_bar_border_width,
|
TAB_BAR_BORDER_WIDTH => ThemeSized::tab_bar_border_width,
|
||||||
TAB_BAR_TEXT_PADDING => ThemeSized::tab_bar_text_padding,
|
TAB_BAR_TEXT_PADDING => ThemeSized::tab_bar_text_padding,
|
||||||
TAB_BAR_GAP => ThemeSized::tab_bar_gap,
|
TAB_BAR_GAP => ThemeSized::tab_bar_gap,
|
||||||
|
TAB_OPACITY => ThemeSized::tab_opacity,
|
||||||
_ => return Err(CphError::UnknownSized(sized.0)),
|
_ => return Err(CphError::UnknownSized(sized.0)),
|
||||||
};
|
};
|
||||||
Ok(sized)
|
Ok(sized)
|
||||||
|
|
@ -2584,10 +2585,16 @@ impl ConfigProxyHandler {
|
||||||
HIGHLIGHT_COLOR => ThemeColor::highlight,
|
HIGHLIGHT_COLOR => ThemeColor::highlight,
|
||||||
TAB_ACTIVE_BACKGROUND_COLOR => ThemeColor::tab_active_background,
|
TAB_ACTIVE_BACKGROUND_COLOR => ThemeColor::tab_active_background,
|
||||||
TAB_ACTIVE_BORDER_COLOR => ThemeColor::tab_active_border,
|
TAB_ACTIVE_BORDER_COLOR => ThemeColor::tab_active_border,
|
||||||
|
TAB_FOCUSED_BACKGROUND_COLOR => ThemeColor::tab_focused_background,
|
||||||
|
TAB_FOCUSED_BORDER_COLOR => ThemeColor::tab_focused_border,
|
||||||
TAB_INACTIVE_BACKGROUND_COLOR => ThemeColor::tab_inactive_background,
|
TAB_INACTIVE_BACKGROUND_COLOR => ThemeColor::tab_inactive_background,
|
||||||
TAB_INACTIVE_BORDER_COLOR => ThemeColor::tab_inactive_border,
|
TAB_INACTIVE_BORDER_COLOR => ThemeColor::tab_inactive_border,
|
||||||
|
TAB_URGENT_BACKGROUND_COLOR => ThemeColor::tab_urgent_background,
|
||||||
|
TAB_URGENT_BORDER_COLOR => ThemeColor::tab_urgent_border,
|
||||||
TAB_ACTIVE_TEXT_COLOR => ThemeColor::tab_active_text,
|
TAB_ACTIVE_TEXT_COLOR => ThemeColor::tab_active_text,
|
||||||
|
TAB_FOCUSED_TEXT_COLOR => ThemeColor::tab_focused_text,
|
||||||
TAB_INACTIVE_TEXT_COLOR => ThemeColor::tab_inactive_text,
|
TAB_INACTIVE_TEXT_COLOR => ThemeColor::tab_inactive_text,
|
||||||
|
TAB_URGENT_TEXT_COLOR => ThemeColor::tab_urgent_text,
|
||||||
TAB_BAR_BACKGROUND_COLOR => ThemeColor::tab_bar_background,
|
TAB_BAR_BACKGROUND_COLOR => ThemeColor::tab_bar_background,
|
||||||
TAB_ATTENTION_BACKGROUND_COLOR => ThemeColor::tab_attention_background,
|
TAB_ATTENTION_BACKGROUND_COLOR => ThemeColor::tab_attention_background,
|
||||||
_ => return Err(CphError::UnknownColor(colorable.0)),
|
_ => return Err(CphError::UnknownColor(colorable.0)),
|
||||||
|
|
@ -3525,6 +3532,12 @@ impl ConfigProxyHandler {
|
||||||
};
|
};
|
||||||
self.state.theme.tab_title_align.set(val);
|
self.state.theme.tab_title_align.set(val);
|
||||||
}
|
}
|
||||||
|
ClientMessage::SetTabFromTop { from_top } => {
|
||||||
|
self.state.theme.tab_from_top.set(from_top);
|
||||||
|
}
|
||||||
|
ClientMessage::SetTabRenderText { render } => {
|
||||||
|
self.state.theme.tab_render_text.set(render);
|
||||||
|
}
|
||||||
ClientMessage::SeatMoveTab { seat, right } => self
|
ClientMessage::SeatMoveTab { seat, right } => self
|
||||||
.handle_seat_move_tab(seat, right)
|
.handle_seat_move_tab(seat, right)
|
||||||
.wrn("seat_move_tab")?,
|
.wrn("seat_move_tab")?,
|
||||||
|
|
|
||||||
|
|
@ -250,7 +250,8 @@ impl CursorUserGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn damage(&self, rect: Rect) {
|
fn damage(&self, rect: Rect) {
|
||||||
self.state.damage2(true, self.hardware_cursor.get(), rect);
|
self.state
|
||||||
|
.damage2(true, self.hardware_cursor.get(), None, rect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,11 @@ pub struct BlurBackdrop {
|
||||||
pub cache: Option<Rc<std::cell::RefCell<Option<BlurCacheEntry>>>>,
|
pub cache: Option<Rc<std::cell::RefCell<Option<BlurCacheEntry>>>>,
|
||||||
pub cache_epoch: u64,
|
pub cache_epoch: u64,
|
||||||
pub cache_pixel_rect: [i32; 4],
|
pub cache_pixel_rect: [i32; 4],
|
||||||
|
/// Corner radius in physical pixels for rounded-rect clipping of the blur.
|
||||||
|
/// 0.0 = no rounding.
|
||||||
|
pub corner_radius: f32,
|
||||||
|
/// Rect size in physical pixels (width, height), used for SDF rounding.
|
||||||
|
pub pixel_size: [f32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for BlurBackdrop {
|
impl std::fmt::Debug for BlurBackdrop {
|
||||||
|
|
@ -363,16 +368,6 @@ pub struct RoundedFillRect {
|
||||||
pub z_order: u32,
|
pub z_order: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoundedFillRect {
|
|
||||||
pub fn effective_color(&self) -> Color {
|
|
||||||
let mut color = self.color;
|
|
||||||
if let Some(alpha) = self.alpha {
|
|
||||||
color = color * alpha;
|
|
||||||
}
|
|
||||||
color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RoundedCopyTexture {
|
pub struct RoundedCopyTexture {
|
||||||
pub tex: Rc<dyn GfxTexture>,
|
pub tex: Rc<dyn GfxTexture>,
|
||||||
pub source: SampleRect,
|
pub source: SampleRect,
|
||||||
|
|
|
||||||
|
|
@ -660,8 +660,8 @@ fn render_blur_backdrop(fb: &Framebuffer, b: &BlurBackdrop) {
|
||||||
.mask
|
.mask
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|m| m.texture.as_gl().map(|t| (m, t)));
|
.and_then(|m| m.texture.as_gl().map(|t| (m, t)));
|
||||||
if let Some((mask, mask_tex_obj)) = mask_gl
|
if b.corner_radius > 0.0
|
||||||
&& !mask_tex_obj.gl.external_only
|
|| matches!(mask_gl.as_ref(), Some((_, mask_tex_obj)) if !mask_tex_obj.gl.external_only)
|
||||||
{
|
{
|
||||||
// Masked composite: src = (blurred * weight, weight); blend = (ONE, ONE_MINUS_SRC_ALPHA).
|
// Masked composite: src = (blurred * weight, weight); blend = (ONE, ONE_MINUS_SRC_ALPHA).
|
||||||
let prog = &ctx.blur_composite;
|
let prog = &ctx.blur_composite;
|
||||||
|
|
@ -670,14 +670,38 @@ fn render_blur_backdrop(fb: &Framebuffer, b: &BlurBackdrop) {
|
||||||
(gles.glBindTexture)(GL_TEXTURE_2D, texs[0]);
|
(gles.glBindTexture)(GL_TEXTURE_2D, texs[0]);
|
||||||
(gles.glUniform1i)(prog.tex, 0);
|
(gles.glUniform1i)(prog.tex, 0);
|
||||||
(gles.glActiveTexture)(GL_TEXTURE1);
|
(gles.glActiveTexture)(GL_TEXTURE1);
|
||||||
|
let has_mask = if let Some((_, mask_tex_obj)) = mask_gl.as_ref() {
|
||||||
|
if !mask_tex_obj.gl.external_only {
|
||||||
(gles.glBindTexture)(GL_TEXTURE_2D, mask_tex_obj.gl.tex);
|
(gles.glBindTexture)(GL_TEXTURE_2D, mask_tex_obj.gl.tex);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
(gles.glBindTexture)(GL_TEXTURE_2D, texs[0]);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(gles.glBindTexture)(GL_TEXTURE_2D, texs[0]);
|
||||||
|
false
|
||||||
|
};
|
||||||
(gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
(gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
(gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
(gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
(gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
(gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
(gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
(gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
(gles.glUniform1i)(prog.mask_tex, 1);
|
(gles.glUniform1i)(prog.mask_tex, 1);
|
||||||
(gles.glUniform1f)(prog.threshold, mask.threshold);
|
(gles.glUniform1f)(
|
||||||
let mask_tc = mask.source.to_points();
|
prog.threshold,
|
||||||
|
mask_gl
|
||||||
|
.as_ref()
|
||||||
|
.map(|(mask, _)| mask.threshold)
|
||||||
|
.unwrap_or(1.0),
|
||||||
|
);
|
||||||
|
(gles.glUniform1f)(prog.corner_radius, b.corner_radius);
|
||||||
|
(gles.glUniform2f)(prog.pixel_size, b.pixel_size[0], b.pixel_size[1]);
|
||||||
|
(gles.glUniform1f)(prog.mask_enabled, if has_mask { 1.0 } else { 0.0 });
|
||||||
|
let mask_tc = mask_gl
|
||||||
|
.as_ref()
|
||||||
|
.filter(|(_, mask_tex_obj)| !mask_tex_obj.gl.external_only)
|
||||||
|
.map(|(mask, _)| mask.source.to_points())
|
||||||
|
.unwrap_or(texcoord);
|
||||||
(gles.glVertexAttribPointer)(
|
(gles.glVertexAttribPointer)(
|
||||||
prog.texcoord as _,
|
prog.texcoord as _,
|
||||||
2,
|
2,
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,9 @@ pub(crate) struct BlurCompositeProg {
|
||||||
pub(crate) tex: GLint,
|
pub(crate) tex: GLint,
|
||||||
pub(crate) mask_tex: GLint,
|
pub(crate) mask_tex: GLint,
|
||||||
pub(crate) threshold: GLint,
|
pub(crate) threshold: GLint,
|
||||||
|
pub(crate) corner_radius: GLint,
|
||||||
|
pub(crate) pixel_size: GLint,
|
||||||
|
pub(crate) mask_enabled: GLint,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct RoundedFillProg {
|
pub(crate) struct RoundedFillProg {
|
||||||
|
|
@ -260,6 +263,9 @@ impl GlRenderContext {
|
||||||
tex: prog.get_uniform_location(c"tex"),
|
tex: prog.get_uniform_location(c"tex"),
|
||||||
mask_tex: prog.get_uniform_location(c"mask_tex"),
|
mask_tex: prog.get_uniform_location(c"mask_tex"),
|
||||||
threshold: prog.get_uniform_location(c"threshold"),
|
threshold: prog.get_uniform_location(c"threshold"),
|
||||||
|
corner_radius: prog.get_uniform_location(c"corner_radius"),
|
||||||
|
pixel_size: prog.get_uniform_location(c"pixel_size"),
|
||||||
|
mask_enabled: prog.get_uniform_location(c"mask_enabled"),
|
||||||
prog,
|
prog,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,20 @@ varying vec2 v_mask_texcoord;
|
||||||
uniform sampler2D tex;
|
uniform sampler2D tex;
|
||||||
uniform sampler2D mask_tex;
|
uniform sampler2D mask_tex;
|
||||||
uniform float threshold;
|
uniform float threshold;
|
||||||
|
uniform float corner_radius;
|
||||||
|
uniform vec2 pixel_size;
|
||||||
|
uniform float mask_enabled;
|
||||||
|
|
||||||
|
float rounded_alpha(vec2 uv) {
|
||||||
|
if (corner_radius <= 0.0)
|
||||||
|
return 1.0;
|
||||||
|
|
||||||
|
float radius = min(corner_radius, min(pixel_size.x, pixel_size.y) * 0.5);
|
||||||
|
vec2 coords = uv * pixel_size;
|
||||||
|
vec2 center = clamp(coords, vec2(radius), pixel_size - vec2(radius));
|
||||||
|
float dist = distance(coords, center);
|
||||||
|
return 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 blurred = texture2D(tex, v_texcoord).rgb;
|
vec3 blurred = texture2D(tex, v_texcoord).rgb;
|
||||||
|
|
@ -12,6 +26,7 @@ void main() {
|
||||||
float in_range = step(0.0, uv.x) * step(uv.x, 1.0)
|
float in_range = step(0.0, uv.x) * step(uv.x, 1.0)
|
||||||
* step(0.0, uv.y) * step(uv.y, 1.0);
|
* step(0.0, uv.y) * step(uv.y, 1.0);
|
||||||
float a = texture2D(mask_tex, clamp(uv, 0.0, 1.0)).a * in_range;
|
float a = texture2D(mask_tex, clamp(uv, 0.0, 1.0)).a * in_range;
|
||||||
float weight = smoothstep(0.0, max(threshold, 0.001), a);
|
float mask_weight = mix(1.0, smoothstep(0.0, max(threshold, 0.001), a), mask_enabled);
|
||||||
|
float weight = mask_weight * rounded_alpha(v_texcoord);
|
||||||
gl_FragColor = vec4(blurred * weight, weight);
|
gl_FragColor = vec4(blurred * weight, weight);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ pub(super) struct BlurMaskRecord<'a> {
|
||||||
pub(super) mask_source_points: [[f32; 2]; 4],
|
pub(super) mask_source_points: [[f32; 2]; 4],
|
||||||
pub(super) target_points: [[f32; 2]; 4],
|
pub(super) target_points: [[f32; 2]; 4],
|
||||||
pub(super) threshold: f32,
|
pub(super) threshold: f32,
|
||||||
|
pub(super) corner_radius: f32,
|
||||||
|
pub(super) pixel_size: [f32; 2],
|
||||||
pub(super) _phantom: std::marker::PhantomData<&'a ()>,
|
pub(super) _phantom: std::marker::PhantomData<&'a ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,6 +147,9 @@ impl VulkanRenderer {
|
||||||
mask: Option<&BlurMaskRecord<'_>>,
|
mask: Option<&BlurMaskRecord<'_>>,
|
||||||
cached_blur: Option<&Rc<VulkanImage>>,
|
cached_blur: Option<&Rc<VulkanImage>>,
|
||||||
out_blur_image: &mut Option<Rc<VulkanImage>>,
|
out_blur_image: &mut Option<Rc<VulkanImage>>,
|
||||||
|
corner_radius: f32,
|
||||||
|
pixel_size: [f32; 2],
|
||||||
|
target_points: [[f32; 2]; 4],
|
||||||
) -> Result<(), VulkanError> {
|
) -> Result<(), VulkanError> {
|
||||||
let [x1, y1, x2, y2] = rect;
|
let [x1, y1, x2, y2] = rect;
|
||||||
let x1 = x1.max(0).min(target.width as i32);
|
let x1 = x1.max(0).min(target.width as i32);
|
||||||
|
|
@ -414,7 +419,7 @@ impl VulkanRenderer {
|
||||||
|
|
||||||
// After cascade: levels[0] in SHADER_READ_ONLY, target in TRANSFER_SRC.
|
// After cascade: levels[0] in SHADER_READ_ONLY, target in TRANSFER_SRC.
|
||||||
|
|
||||||
if let Some(mask) = mask {
|
if mask.is_some() || corner_radius > 0.0 {
|
||||||
// Masked composite path: restore target to COLOR_ATTACHMENT and
|
// Masked composite path: restore target to COLOR_ATTACHMENT and
|
||||||
// draw the composite shader sampling levels[0] + mask.
|
// draw the composite shader sampling levels[0] + mask.
|
||||||
do_barriers(&[barrier(
|
do_barriers(&[barrier(
|
||||||
|
|
@ -464,18 +469,22 @@ impl VulkanRenderer {
|
||||||
|
|
||||||
// Identity uv across blurred level 0 (full image is the blurred rect).
|
// Identity uv across blurred level 0 (full image is the blurred rect).
|
||||||
let blurred_tc: [[f32; 2]; 4] = [[1.0, 0.0], [0.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
|
let blurred_tc: [[f32; 2]; 4] = [[1.0, 0.0], [0.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
|
||||||
|
let mask_points = mask.map(|m| m.mask_source_points).unwrap_or(blurred_tc);
|
||||||
let push = BlurCompositePushConstants {
|
let push = BlurCompositePushConstants {
|
||||||
pos: mask.target_points,
|
pos: mask.map(|m| m.target_points).unwrap_or(target_points),
|
||||||
blurred_tex_pos: blurred_tc,
|
blurred_tex_pos: blurred_tc,
|
||||||
mask_tex_pos: mask.mask_source_points,
|
mask_tex_pos: mask_points,
|
||||||
threshold: mask.threshold,
|
threshold: mask.map(|m| m.threshold).unwrap_or(1.0),
|
||||||
|
corner_radius,
|
||||||
|
pixel_size,
|
||||||
|
mask_enabled: if mask.is_some() { 1.0 } else { 0.0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
let blurred_image_info = DescriptorImageInfo::default()
|
let blurred_image_info = DescriptorImageInfo::default()
|
||||||
.image_view(levels[0].texture_view)
|
.image_view(levels[0].texture_view)
|
||||||
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
|
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
|
||||||
let mask_image_info = DescriptorImageInfo::default()
|
let mask_image_info = DescriptorImageInfo::default()
|
||||||
.image_view(mask.mask_view)
|
.image_view(mask.map(|m| m.mask_view).unwrap_or(levels[0].texture_view))
|
||||||
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
|
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
|
||||||
let writes = [
|
let writes = [
|
||||||
WriteDescriptorSet::default()
|
WriteDescriptorSet::default()
|
||||||
|
|
@ -671,6 +680,9 @@ impl VulkanRenderer {
|
||||||
blurred_tex_pos: blurred_tc,
|
blurred_tex_pos: blurred_tc,
|
||||||
mask_tex_pos: mask.mask_source_points,
|
mask_tex_pos: mask.mask_source_points,
|
||||||
threshold: mask.threshold,
|
threshold: mask.threshold,
|
||||||
|
corner_radius: mask.corner_radius,
|
||||||
|
pixel_size: mask.pixel_size,
|
||||||
|
mask_enabled: 1.0,
|
||||||
};
|
};
|
||||||
let blurred_image_info = DescriptorImageInfo::default()
|
let blurred_image_info = DescriptorImageInfo::default()
|
||||||
.image_view(cached.texture_view)
|
.image_view(cached.texture_view)
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,15 @@ use {
|
||||||
semaphore::VulkanSemaphore,
|
semaphore::VulkanSemaphore,
|
||||||
shaders::{
|
shaders::{
|
||||||
BLUR_COMPOSITE_FRAG, BLUR_COMPOSITE_VERT, BLUR_DOWN_FRAG, BLUR_UP_FRAG, BLUR_VERT,
|
BLUR_COMPOSITE_FRAG, BLUR_COMPOSITE_VERT, BLUR_DOWN_FRAG, BLUR_UP_FRAG, BLUR_VERT,
|
||||||
BlurCompositePushConstants, BlurPushConstants,
|
BlurCompositePushConstants, BlurPushConstants, ColorManagementData, EotfArgs,
|
||||||
ColorManagementData, EotfArgs, FILL_FRAG, FILL_VERT, FillPushConstants,
|
FILL_FRAG, FILL_VERT, FillPushConstants, InvEotfArgs, LEGACY_FILL_FRAG,
|
||||||
InvEotfArgs, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, LEGACY_ROUNDED_FILL_FRAG,
|
LEGACY_FILL_VERT, LEGACY_ROUNDED_FILL_FRAG, LEGACY_ROUNDED_FILL_VERT,
|
||||||
LEGACY_ROUNDED_FILL_VERT, LEGACY_ROUNDED_TEX_FRAG, LEGACY_ROUNDED_TEX_VERT,
|
LEGACY_ROUNDED_TEX_FRAG, LEGACY_ROUNDED_TEX_VERT, LEGACY_TEX_FRAG, LEGACY_TEX_VERT,
|
||||||
LEGACY_TEX_FRAG, LEGACY_TEX_VERT, LegacyFillPushConstants,
|
LegacyFillPushConstants, LegacyRoundedFillPushConstants,
|
||||||
LegacyRoundedFillPushConstants, LegacyRoundedTexPushConstants,
|
LegacyRoundedTexPushConstants, LegacyTexPushConstants, OUT_FRAG, OUT_VERT,
|
||||||
LegacyTexPushConstants, OUT_FRAG, OUT_VERT, OutPushConstants, ROUNDED_FILL_FRAG,
|
OutPushConstants, ROUNDED_FILL_FRAG, ROUNDED_FILL_VERT, ROUNDED_TEX_FRAG,
|
||||||
ROUNDED_FILL_VERT, ROUNDED_TEX_FRAG, ROUNDED_TEX_VERT, RoundedFillPushConstants,
|
ROUNDED_TEX_VERT, RoundedFillPushConstants, RoundedTexPushConstants, TEX_FRAG,
|
||||||
RoundedTexPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants, TexVertex,
|
TEX_VERT, TexPushConstants, TexVertex, VulkanShader,
|
||||||
VulkanShader,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
io_uring::IoUring,
|
io_uring::IoUring,
|
||||||
|
|
@ -241,6 +240,8 @@ struct VulkanBlurOp {
|
||||||
cache: Option<Rc<RefCell<Option<crate::gfx_api::BlurCacheEntry>>>>,
|
cache: Option<Rc<RefCell<Option<crate::gfx_api::BlurCacheEntry>>>>,
|
||||||
cache_epoch: u64,
|
cache_epoch: u64,
|
||||||
cache_pixel_rect: [i32; 4],
|
cache_pixel_rect: [i32; 4],
|
||||||
|
corner_radius: f32,
|
||||||
|
pixel_size: [f32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VulkanBlurMask {
|
struct VulkanBlurMask {
|
||||||
|
|
@ -1569,6 +1570,8 @@ impl VulkanRenderer {
|
||||||
cache: b.cache.clone(),
|
cache: b.cache.clone(),
|
||||||
cache_epoch: b.cache_epoch,
|
cache_epoch: b.cache_epoch,
|
||||||
cache_pixel_rect: b.cache_pixel_rect,
|
cache_pixel_rect: b.cache_pixel_rect,
|
||||||
|
corner_radius: b.corner_radius,
|
||||||
|
pixel_size: b.pixel_size,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2305,6 +2308,8 @@ impl VulkanRenderer {
|
||||||
mask_source_points: m.source.to_points(),
|
mask_source_points: m.source.to_points(),
|
||||||
target_points: blur.rect.to_points(),
|
target_points: blur.rect.to_points(),
|
||||||
threshold: m.threshold,
|
threshold: m.threshold,
|
||||||
|
corner_radius: blur.corner_radius,
|
||||||
|
pixel_size: blur.pixel_size,
|
||||||
_phantom: std::marker::PhantomData,
|
_phantom: std::marker::PhantomData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -2341,6 +2346,9 @@ impl VulkanRenderer {
|
||||||
mask_record.as_ref(),
|
mask_record.as_ref(),
|
||||||
cached_blur.as_ref(),
|
cached_blur.as_ref(),
|
||||||
&mut produced_blur,
|
&mut produced_blur,
|
||||||
|
blur.corner_radius,
|
||||||
|
blur.pixel_size,
|
||||||
|
blur.rect.to_points(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// On a masked cache miss, store the freshly-blurred image
|
// On a masked cache miss, store the freshly-blurred image
|
||||||
|
|
@ -2922,7 +2930,23 @@ impl VulkanRenderer {
|
||||||
// but they do paint pixels and need paint regions.
|
// but they do paint pixels and need paint regions.
|
||||||
(false, rf.rect)
|
(false, rf.rect)
|
||||||
}
|
}
|
||||||
GfxApiOpt::RoundedCopyTexture(ct) => (false, ct.target),
|
GfxApiOpt::RoundedCopyTexture(c) => {
|
||||||
|
let opaque = 'opaque: {
|
||||||
|
if let Some(a) = c.alpha
|
||||||
|
&& a < 1.0
|
||||||
|
{
|
||||||
|
break 'opaque false;
|
||||||
|
}
|
||||||
|
if !c.opaque {
|
||||||
|
let tex = c.tex.as_vk(&self.device.device)?;
|
||||||
|
if tex.format.has_alpha {
|
||||||
|
break 'opaque false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
};
|
||||||
|
(opaque, c.target)
|
||||||
|
}
|
||||||
GfxApiOpt::BlurBackdrop(b) => {
|
GfxApiOpt::BlurBackdrop(b) => {
|
||||||
blur_rects.push(b.rect.to_rect(width, height));
|
blur_rects.push(b.rect.to_rect(width, height));
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,9 @@ pub struct BlurCompositePushConstants {
|
||||||
pub blurred_tex_pos: [[f32; 2]; 4],
|
pub blurred_tex_pos: [[f32; 2]; 4],
|
||||||
pub mask_tex_pos: [[f32; 2]; 4],
|
pub mask_tex_pos: [[f32; 2]; 4],
|
||||||
pub threshold: f32,
|
pub threshold: f32,
|
||||||
|
pub corner_radius: f32,
|
||||||
|
pub pixel_size: [f32; 2],
|
||||||
|
pub mask_enabled: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Packed for BlurCompositePushConstants {}
|
unsafe impl Packed for BlurCompositePushConstants {}
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,33 @@ layout(set = 0, binding = 1) uniform sampler2D mask_tex;
|
||||||
|
|
||||||
layout(push_constant, std430) uniform Data {
|
layout(push_constant, std430) uniform Data {
|
||||||
layout(offset = 96) float threshold;
|
layout(offset = 96) float threshold;
|
||||||
|
layout(offset = 100) float corner_radius;
|
||||||
|
layout(offset = 104) vec2 pixel_size;
|
||||||
|
layout(offset = 112) float mask_enabled;
|
||||||
} data;
|
} data;
|
||||||
|
|
||||||
layout(location = 0) in vec2 v_blurred_tex_pos;
|
layout(location = 0) in vec2 v_blurred_tex_pos;
|
||||||
layout(location = 1) in vec2 v_mask_tex_pos;
|
layout(location = 1) in vec2 v_mask_tex_pos;
|
||||||
layout(location = 0) out vec4 out_color;
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
float rounded_alpha(vec2 uv) {
|
||||||
|
if (data.corner_radius <= 0.0)
|
||||||
|
return 1.0;
|
||||||
|
|
||||||
|
float radius = min(data.corner_radius, min(data.pixel_size.x, data.pixel_size.y) * 0.5);
|
||||||
|
vec2 coords = uv * data.pixel_size;
|
||||||
|
vec2 center = clamp(coords, vec2(radius), data.pixel_size - vec2(radius));
|
||||||
|
float dist = distance(coords, center);
|
||||||
|
return 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 blurred = textureLod(blurred_tex, v_blurred_tex_pos, 0).rgb;
|
vec3 blurred = textureLod(blurred_tex, v_blurred_tex_pos, 0).rgb;
|
||||||
vec2 uv = v_mask_tex_pos;
|
vec2 uv = v_mask_tex_pos;
|
||||||
float in_range = step(0.0, uv.x) * step(uv.x, 1.0)
|
float in_range = step(0.0, uv.x) * step(uv.x, 1.0)
|
||||||
* step(0.0, uv.y) * step(uv.y, 1.0);
|
* step(0.0, uv.y) * step(uv.y, 1.0);
|
||||||
float a = textureLod(mask_tex, clamp(uv, 0.0, 1.0), 0).a * in_range;
|
float a = textureLod(mask_tex, clamp(uv, 0.0, 1.0), 0).a * in_range;
|
||||||
float weight = smoothstep(0.0, max(data.threshold, 0.001), a);
|
float mask_weight = mix(1.0, smoothstep(0.0, max(data.threshold, 0.001), a), data.mask_enabled);
|
||||||
|
float weight = mask_weight * rounded_alpha(v_blurred_tex_pos);
|
||||||
out_color = vec4(blurred * weight, weight);
|
out_color = vec4(blurred * weight, weight);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,6 +1,6 @@
|
||||||
302a9f250bdc4f8e0e71a9f77c9a8a7aa55fd003bc91c2422a700c4abd83f54e src/gfx_apis/vulkan/shaders/alpha_modes.glsl
|
302a9f250bdc4f8e0e71a9f77c9a8a7aa55fd003bc91c2422a700c4abd83f54e src/gfx_apis/vulkan/shaders/alpha_modes.glsl
|
||||||
65acbe7a6496279fa22f520ad2036d3e14a7cb1707c6a509ce7858adc4a2dcba src/gfx_apis/vulkan/shaders/blur.vert
|
65acbe7a6496279fa22f520ad2036d3e14a7cb1707c6a509ce7858adc4a2dcba src/gfx_apis/vulkan/shaders/blur.vert
|
||||||
16ad6f1eb029ccce5e0204a7d79709b05a8a708133feaf8bb20a24371de25ed7 src/gfx_apis/vulkan/shaders/blur_composite.frag
|
e2ff39a764534debaa5857f7434fb552e9c10a83bab5d706c14240d4e704f7d2 src/gfx_apis/vulkan/shaders/blur_composite.frag
|
||||||
6399e23afa2e07c98b9fd1a4e853ea974a9958547ce65734846483bd7cbc8461 src/gfx_apis/vulkan/shaders/blur_composite.vert
|
6399e23afa2e07c98b9fd1a4e853ea974a9958547ce65734846483bd7cbc8461 src/gfx_apis/vulkan/shaders/blur_composite.vert
|
||||||
a04b2453c39efb018754fc25d45a369b5813359c55fad1c99020804cbb3a18e0 src/gfx_apis/vulkan/shaders/blur_down.frag
|
a04b2453c39efb018754fc25d45a369b5813359c55fad1c99020804cbb3a18e0 src/gfx_apis/vulkan/shaders/blur_down.frag
|
||||||
f6d51f3b5410387d1474529c44e71bfdc31ceb80174ea6e3e4c2df30d03f11c3 src/gfx_apis/vulkan/shaders/blur_up.frag
|
f6d51f3b5410387d1474529c44e71bfdc31ceb80174ea6e3e4c2df30d03f11c3 src/gfx_apis/vulkan/shaders/blur_up.frag
|
||||||
|
|
|
||||||
|
|
@ -1494,7 +1494,7 @@ impl WlSurface {
|
||||||
if let Some(tl) = self.toplevel.get() {
|
if let Some(tl) = self.toplevel.get() {
|
||||||
damage = damage.intersect(tl.node_absolute_position());
|
damage = damage.intersect(tl.node_absolute_position());
|
||||||
}
|
}
|
||||||
self.client.state.damage(damage);
|
self.client.state.damage_for_surface(self, damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.visible.get() {
|
if self.visible.get() {
|
||||||
|
|
@ -1520,7 +1520,7 @@ impl WlSurface {
|
||||||
// Frame requests must be dispatched at the highest possible frame rate.
|
// Frame requests must be dispatched at the highest possible frame rate.
|
||||||
// Therefore we must trigger a vsync of the output as soon as possible.
|
// Therefore we must trigger a vsync of the output as soon as possible.
|
||||||
let rect = output.global.pos.get();
|
let rect = output.global.pos.get();
|
||||||
self.client.state.damage(rect);
|
self.client.state.damage_for_surface(self, rect);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if fifo_barrier_set {
|
if fifo_barrier_set {
|
||||||
|
|
@ -1555,7 +1555,7 @@ impl WlSurface {
|
||||||
had_texture
|
had_texture
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_damage(&self, pending: &PendingState) {
|
fn apply_damage(self: &Rc<Self>, pending: &PendingState) {
|
||||||
let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds());
|
let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds());
|
||||||
let pos = self.buffer_abs_pos.get();
|
let pos = self.buffer_abs_pos.get();
|
||||||
let apply_damage = |pos: Rect| {
|
let apply_damage = |pos: Rect| {
|
||||||
|
|
@ -1564,7 +1564,7 @@ impl WlSurface {
|
||||||
if let Some(bounds) = bounds {
|
if let Some(bounds) = bounds {
|
||||||
damage = damage.intersect(bounds);
|
damage = damage.intersect(bounds);
|
||||||
}
|
}
|
||||||
self.client.state.damage(damage);
|
self.client.state.damage_for_surface(self, damage);
|
||||||
} else {
|
} else {
|
||||||
let matrix = self.damage_matrix.get();
|
let matrix = self.damage_matrix.get();
|
||||||
if let Some(buffer) = self.buffer.get() {
|
if let Some(buffer) = self.buffer.get() {
|
||||||
|
|
@ -1577,7 +1577,7 @@ impl WlSurface {
|
||||||
if let Some(bounds) = bounds {
|
if let Some(bounds) = bounds {
|
||||||
damage = damage.intersect(bounds);
|
damage = damage.intersect(bounds);
|
||||||
}
|
}
|
||||||
self.client.state.damage(damage);
|
self.client.state.damage_for_surface(self, damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for damage in &pending.surface_damage {
|
for damage in &pending.surface_damage {
|
||||||
|
|
@ -1590,7 +1590,7 @@ impl WlSurface {
|
||||||
damage = Rect::new_saturating(x1, y1, x2, y2);
|
damage = Rect::new_saturating(x1, y1, x2, y2);
|
||||||
}
|
}
|
||||||
damage = damage.intersect(bounds.unwrap_or(pos));
|
damage = damage.intersect(bounds.unwrap_or(pos));
|
||||||
self.client.state.damage(damage);
|
self.client.state.damage_for_surface(self, damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,6 @@ where
|
||||||
dx * dx + dy * dy
|
dx * dx + dy * dy
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(dead_code)]
|
|
||||||
pub fn contains_rect<U>(&self, rect: &Rect<U>) -> bool
|
pub fn contains_rect<U>(&self, rect: &Rect<U>) -> bool
|
||||||
where
|
where
|
||||||
U: Tag,
|
U: Tag,
|
||||||
|
|
|
||||||
123
src/renderer.rs
123
src/renderer.rs
|
|
@ -346,7 +346,7 @@ impl Renderer<'_> {
|
||||||
self.render_tl_aux(placeholder.tl_data(), bounds, true);
|
self.render_tl_aux(placeholder.tl_data(), bounds, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tab_bar(&mut self, tab_bar: &TabBar, x: i32, y: i32, _container_width: i32) {
|
fn render_tab_bar(&mut self, tab_bar: &TabBar, x: i32, y: i32, container_width: i32) {
|
||||||
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
||||||
let srgb = &srgb_srgb.linear;
|
let srgb = &srgb_srgb.linear;
|
||||||
let perceptual = RenderIntent::Perceptual;
|
let perceptual = RenderIntent::Perceptual;
|
||||||
|
|
@ -355,35 +355,79 @@ impl Renderer<'_> {
|
||||||
let radius = self.state.theme.sizes.tab_bar_radius.get();
|
let radius = self.state.theme.sizes.tab_bar_radius.get();
|
||||||
let border_width = self.state.theme.sizes.tab_bar_border_width.get();
|
let border_width = self.state.theme.sizes.tab_bar_border_width.get();
|
||||||
let text_padding = self.state.theme.sizes.tab_bar_text_padding.get();
|
let text_padding = self.state.theme.sizes.tab_bar_text_padding.get();
|
||||||
|
let padding = self.state.theme.sizes.tab_bar_padding.get();
|
||||||
let bar_height = tab_bar.height;
|
let bar_height = tab_bar.height;
|
||||||
let render_scale = tab_bar.render_scale;
|
let render_scale = tab_bar.render_scale;
|
||||||
|
let tab_opacity = self.state.theme.sizes.tab_opacity.get() as f32 / 100.0;
|
||||||
|
let render_text = self.state.theme.tab_render_text.get();
|
||||||
|
|
||||||
|
// Compute static positions for non-destroying entries.
|
||||||
|
let active_entries: Vec<usize> = tab_bar
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, e)| !e.destroying)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.collect();
|
||||||
|
let n = active_entries.len() as i32;
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let total_padding = padding * (n + 1);
|
||||||
|
let available = (container_width - total_padding).max(0);
|
||||||
|
let per_tab = available / n;
|
||||||
|
let mut remainder = available - per_tab * n;
|
||||||
|
|
||||||
|
// Build a map: entry index → (x, width) in pixels.
|
||||||
|
let mut positions: Vec<(i32, i32)> = vec![(0, 0); tab_bar.entries.len()];
|
||||||
|
let mut tab_x = padding;
|
||||||
|
for &idx in &active_entries {
|
||||||
|
let w = if remainder > 0 {
|
||||||
|
remainder -= 1;
|
||||||
|
per_tab + 1
|
||||||
|
} else {
|
||||||
|
per_tab
|
||||||
|
};
|
||||||
|
positions[idx] = (tab_x, w);
|
||||||
|
tab_x += w + padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
let render_entry =
|
||||||
|
|renderer: &mut Self, idx: usize, entry: &crate::tree::tab_bar::TabBarEntry| {
|
||||||
|
let fade = entry.fade_opacity.value() * tab_opacity;
|
||||||
|
if fade < 1e-4 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ex, ew) = positions[idx];
|
||||||
|
if ew < 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (bg_color, border_color, _text_color) = entry.blended_colors(renderer.state);
|
||||||
|
let bg_color = bg_color * fade;
|
||||||
|
let border_color = border_color * fade;
|
||||||
|
|
||||||
// Vulkan sorts ops: Fill < RoundedFill (by z_order, color) < Tex/RoundedTex (by index).
|
|
||||||
// We use:
|
|
||||||
// FillRect – tiny strip for Vulkan paint regions (hidden)
|
|
||||||
// RoundedFillRect z0 – solid rounded bg
|
|
||||||
// RoundedFillRect z1 – rounded border ring (on top of bg)
|
|
||||||
// RoundedCopyTexture – title text (on top of everything)
|
|
||||||
for entry in &tab_bar.entries {
|
|
||||||
let (bg_color, border_color, _text_color) = TabBar::entry_colors(self.state, entry);
|
|
||||||
let ex = entry.x.get();
|
|
||||||
let ew = entry.width.get();
|
|
||||||
let tab_rect = Rect::new_sized_saturating(ex, 0, ew, bar_height);
|
let tab_rect = Rect::new_sized_saturating(ex, 0, ew, bar_height);
|
||||||
|
|
||||||
|
// Per-tab blur backdrop.
|
||||||
|
let cfg = renderer.state.blur_config.get();
|
||||||
|
if cfg.passes > 0 {
|
||||||
|
let blur_rect = tab_rect.move_(x, y);
|
||||||
|
let scaled = renderer.base.scale_rect(blur_rect);
|
||||||
|
renderer.base.push_blur_backdrop2(
|
||||||
|
scaled,
|
||||||
|
cfg.passes,
|
||||||
|
cfg.size,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
radius as f32 * renderer.base.scalef as f32,
|
||||||
|
);
|
||||||
|
}
|
||||||
let tab_cr = CornerRadius::from(radius as f32);
|
let tab_cr = CornerRadius::from(radius as f32);
|
||||||
|
|
||||||
// Tiny FillRect strip to establish Vulkan paint regions (visually hidden
|
renderer.base.fill_rounded_rect_z(
|
||||||
// behind the RoundedFillRect bg that renders later).
|
|
||||||
let strip = Rect::new_sized_saturating(
|
|
||||||
ex + radius,
|
|
||||||
bar_height / 2,
|
|
||||||
(ew - 2 * radius).max(1),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
self.base
|
|
||||||
.fill_boxes2(slice::from_ref(&strip), &bg_color, srgb, perceptual, x, y);
|
|
||||||
|
|
||||||
// Rounded solid bg fill (z_order=0, renders first among RoundedFill).
|
|
||||||
self.base.fill_rounded_rect_z(
|
|
||||||
tab_rect.move_(x, y),
|
tab_rect.move_(x, y),
|
||||||
&bg_color,
|
&bg_color,
|
||||||
None,
|
None,
|
||||||
|
|
@ -394,9 +438,8 @@ impl Renderer<'_> {
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Rounded border ring on top (z_order=1, renders after bg).
|
|
||||||
if border_width > 0 {
|
if border_width > 0 {
|
||||||
self.base.fill_rounded_rect_z(
|
renderer.base.fill_rounded_rect_z(
|
||||||
tab_rect.move_(x, y),
|
tab_rect.move_(x, y),
|
||||||
&border_color,
|
&border_color,
|
||||||
None,
|
None,
|
||||||
|
|
@ -408,7 +451,10 @@ impl Renderer<'_> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title text as RoundedCopyTexture (sorts after all RoundedFill).
|
if !render_text {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let tex_ref = entry.title_texture.borrow();
|
let tex_ref = entry.title_texture.borrow();
|
||||||
if let Some(tex) = tex_ref.as_ref()
|
if let Some(tex) = tex_ref.as_ref()
|
||||||
&& let Some(texture) = tex.texture()
|
&& let Some(texture) = tex.texture()
|
||||||
|
|
@ -417,7 +463,7 @@ impl Renderer<'_> {
|
||||||
let (tw, _th) = texture.size();
|
let (tw, _th) = texture.size();
|
||||||
let tex_width = (tw as f64 / render_scale.to_f64()).round() as i32;
|
let tex_width = (tw as f64 / render_scale.to_f64()).round() as i32;
|
||||||
let tab_inner = ew - 2 * (text_padding + border_width);
|
let tab_inner = ew - 2 * (text_padding + border_width);
|
||||||
let text_x = match self.state.theme.tab_title_align.get() {
|
let text_x = match renderer.state.theme.tab_title_align.get() {
|
||||||
TabTitleAlign::Start => x + ex + text_padding + border_width,
|
TabTitleAlign::Start => x + ex + text_padding + border_width,
|
||||||
TabTitleAlign::Center => {
|
TabTitleAlign::Center => {
|
||||||
x + ex
|
x + ex
|
||||||
|
|
@ -430,8 +476,8 @@ impl Renderer<'_> {
|
||||||
end_x.max(x + ex + border_width)
|
end_x.max(x + ex + border_width)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let (tx, ty) = self.base.scale_point(text_x, y);
|
let (tx, ty) = renderer.base.scale_point(text_x, y);
|
||||||
self.base.render_rounded_texture(
|
renderer.base.render_rounded_texture(
|
||||||
&texture,
|
&texture,
|
||||||
None,
|
None,
|
||||||
tx,
|
tx,
|
||||||
|
|
@ -443,12 +489,26 @@ impl Renderer<'_> {
|
||||||
None,
|
None,
|
||||||
AcquireSync::None,
|
AcquireSync::None,
|
||||||
ReleaseSync::None,
|
ReleaseSync::None,
|
||||||
self.state.color_manager.srgb_gamma22(),
|
renderer.state.color_manager.srgb_gamma22(),
|
||||||
perceptual,
|
perceptual,
|
||||||
AlphaMode::PremultipliedElectrical,
|
AlphaMode::PremultipliedElectrical,
|
||||||
CornerRadius::from(0.0_f32),
|
CornerRadius::from(0.0_f32),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render unfocused entries first, then focused on top.
|
||||||
|
for (idx, entry) in tab_bar.entries.iter().enumerate() {
|
||||||
|
if entry.destroying || entry.focused_anim.target() >= 0.5 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
render_entry(self, idx, entry);
|
||||||
|
}
|
||||||
|
for (idx, entry) in tab_bar.entries.iter().enumerate() {
|
||||||
|
if entry.destroying || entry.focused_anim.target() < 0.5 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
render_entry(self, idx, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,7 +526,6 @@ impl Renderer<'_> {
|
||||||
self.render_container_decorations(container, x, y);
|
self.render_container_decorations(container, x, y);
|
||||||
|
|
||||||
if let Some(child) = container.mono_child.get() {
|
if let Some(child) = container.mono_child.get() {
|
||||||
// Render tab bar if present.
|
|
||||||
{
|
{
|
||||||
let tab_bar = container.tab_bar.borrow();
|
let tab_bar = container.tab_bar.borrow();
|
||||||
if let Some(tb) = tab_bar.as_ref() {
|
if let Some(tb) = tab_bar.as_ref() {
|
||||||
|
|
|
||||||
|
|
@ -411,6 +411,19 @@ impl RendererBase<'_> {
|
||||||
mask: Option<BlurMask>,
|
mask: Option<BlurMask>,
|
||||||
cache: Option<Rc<std::cell::RefCell<Option<crate::gfx_api::BlurCacheEntry>>>>,
|
cache: Option<Rc<std::cell::RefCell<Option<crate::gfx_api::BlurCacheEntry>>>>,
|
||||||
cache_epoch: u64,
|
cache_epoch: u64,
|
||||||
|
) {
|
||||||
|
self.push_blur_backdrop2(rect, passes, offset, mask, cache, cache_epoch, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_blur_backdrop2(
|
||||||
|
&mut self,
|
||||||
|
rect: Rect,
|
||||||
|
passes: u8,
|
||||||
|
offset: f32,
|
||||||
|
mask: Option<BlurMask>,
|
||||||
|
cache: Option<Rc<std::cell::RefCell<Option<crate::gfx_api::BlurCacheEntry>>>>,
|
||||||
|
cache_epoch: u64,
|
||||||
|
corner_radius: f32,
|
||||||
) {
|
) {
|
||||||
let target = FramebufferRect::new(
|
let target = FramebufferRect::new(
|
||||||
rect.x1() as f32,
|
rect.x1() as f32,
|
||||||
|
|
@ -422,6 +435,10 @@ impl RendererBase<'_> {
|
||||||
self.fb_height,
|
self.fb_height,
|
||||||
);
|
);
|
||||||
let cache_pixel_rect = [rect.x1(), rect.y1(), rect.x2(), rect.y2()];
|
let cache_pixel_rect = [rect.x1(), rect.y1(), rect.x2(), rect.y2()];
|
||||||
|
let pixel_size = [
|
||||||
|
(rect.x2() - rect.x1()) as f32,
|
||||||
|
(rect.y2() - rect.y1()) as f32,
|
||||||
|
];
|
||||||
self.ops.push(GfxApiOpt::BlurBackdrop(BlurBackdrop {
|
self.ops.push(GfxApiOpt::BlurBackdrop(BlurBackdrop {
|
||||||
rect: target,
|
rect: target,
|
||||||
passes,
|
passes,
|
||||||
|
|
@ -430,6 +447,8 @@ impl RendererBase<'_> {
|
||||||
cache,
|
cache,
|
||||||
cache_epoch,
|
cache_epoch,
|
||||||
cache_pixel_rect,
|
cache_pixel_rect,
|
||||||
|
corner_radius,
|
||||||
|
pixel_size,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
48
src/state.rs
48
src/state.rs
|
|
@ -306,10 +306,10 @@ pub struct State {
|
||||||
pub hyprland_global_shortcuts: CopyHashMap<(String, String), Rc<HyprlandGlobalShortcutV1>>,
|
pub hyprland_global_shortcuts: CopyHashMap<(String, String), Rc<HyprlandGlobalShortcutV1>>,
|
||||||
pub layer_rules: RefCell<Vec<jay_config::_private::LayerRuleIpc>>,
|
pub layer_rules: RefCell<Vec<jay_config::_private::LayerRuleIpc>>,
|
||||||
pub blur_config: Cell<jay_config::_private::BlurConfigIpc>,
|
pub blur_config: Cell<jay_config::_private::BlurConfigIpc>,
|
||||||
pub blur_cache_epoch: NumCell<u64>,
|
|
||||||
pub animations_config: Cell<jay_config::_private::AnimationsConfigIpc>,
|
pub animations_config: Cell<jay_config::_private::AnimationsConfigIpc>,
|
||||||
pub active_animations: RefCell<Vec<std::rc::Weak<dyn ToplevelNode>>>,
|
pub active_animations: RefCell<Vec<std::rc::Weak<dyn ToplevelNode>>>,
|
||||||
pub close_snapshots: RefCell<Vec<Rc<crate::animation::Snapshot>>>,
|
pub close_snapshots: RefCell<Vec<Rc<crate::animation::Snapshot>>>,
|
||||||
|
pub tab_animation_containers: RefCell<Vec<std::rc::Weak<crate::tree::ContainerNode>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Drop for State {
|
// impl Drop for State {
|
||||||
|
|
@ -1046,10 +1046,25 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn damage(&self, rect: Rect) {
|
pub fn damage(&self, rect: Rect) {
|
||||||
self.damage2(false, false, rect);
|
self.damage2(false, false, None, rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn damage2(&self, cursor: bool, skip_hc: bool, rect: Rect) {
|
/// Damage caused by `source` (or any of its subsurface descendants). The
|
||||||
|
/// source's blur-cache will not be invalidated for this damage, since a
|
||||||
|
/// surface's own contents don't sit behind its blur backdrop. Other
|
||||||
|
/// blurring surfaces are still invalidated normally.
|
||||||
|
pub fn damage_for_surface(&self, source: &Rc<crate::ifs::wl_surface::WlSurface>, rect: Rect) {
|
||||||
|
let root_id = source.get_root().node_id;
|
||||||
|
self.damage2(false, false, Some(root_id), rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn damage2(
|
||||||
|
&self,
|
||||||
|
cursor: bool,
|
||||||
|
skip_hc: bool,
|
||||||
|
source_root: Option<crate::ifs::wl_surface::SurfaceNodeId>,
|
||||||
|
rect: Rect,
|
||||||
|
) {
|
||||||
if rect.is_empty() {
|
if rect.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1059,11 +1074,18 @@ impl State {
|
||||||
for surface in layer.iter() {
|
for surface in layer.iter() {
|
||||||
if surface.blur.get() && surface.node_absolute_position().intersects(&rect)
|
if surface.blur.get() && surface.node_absolute_position().intersects(&rect)
|
||||||
{
|
{
|
||||||
|
// Skip invalidation when the damage comes from the
|
||||||
|
// bar itself (or its subsurfaces) — the bar's own
|
||||||
|
// pixels aren't part of its own blur backdrop.
|
||||||
|
if source_root != Some(surface.surface.node_id) {
|
||||||
surface.blur_cache_epoch.fetch_add(1);
|
surface.blur_cache_epoch.fetch_add(1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if surface.blur.get() && surface.blur_popups.get() {
|
if surface.blur.get() && surface.blur_popups.get() {
|
||||||
surface.for_each_popup(|popup| {
|
surface.for_each_popup(|popup| {
|
||||||
if popup.node_absolute_position().intersects(&rect) {
|
if popup.node_absolute_position().intersects(&rect)
|
||||||
|
&& source_root != Some(popup.xdg.surface.node_id)
|
||||||
|
{
|
||||||
popup.blur_cache_epoch.fetch_add(1);
|
popup.blur_cache_epoch.fetch_add(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1518,13 +1540,9 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut snapshots = self.close_snapshots.borrow_mut();
|
let mut snapshots = self.close_snapshots.borrow_mut();
|
||||||
if snapshots.is_empty() {
|
if !snapshots.is_empty() {
|
||||||
return;
|
|
||||||
}
|
|
||||||
snapshots.retain(|snap| {
|
snapshots.retain(|snap| {
|
||||||
if snap.close_progress(self).is_none() {
|
if snap.close_progress(self).is_none() {
|
||||||
// Final damage so the snapshot's last-rendered position gets
|
|
||||||
// repainted (clearing any leftover pixels).
|
|
||||||
if let Some(output) = snap.output.upgrade() {
|
if let Some(output) = snap.output.upgrade() {
|
||||||
self.damage(output.global.pos.get());
|
self.damage(output.global.pos.get());
|
||||||
}
|
}
|
||||||
|
|
@ -1536,6 +1554,18 @@ impl State {
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
let mut tab_containers = self.tab_animation_containers.borrow_mut();
|
||||||
|
if !tab_containers.is_empty() {
|
||||||
|
tab_containers.retain(|weak| {
|
||||||
|
let Some(container) = weak.upgrade() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
container.tick_tab_animations()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn output_extents_changed(&self) {
|
pub fn output_extents_changed(&self) {
|
||||||
self.root.update_extents();
|
self.root.update_extents();
|
||||||
|
|
|
||||||
26
src/theme.rs
26
src/theme.rs
|
|
@ -183,6 +183,14 @@ impl Color {
|
||||||
self.a >= 1.0
|
self.a >= 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn alpha(&self) -> f32 {
|
||||||
|
self.a
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_alpha(&mut self, a: f32) {
|
||||||
|
self.a = a;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_gray_srgb(g: u8) -> Self {
|
pub fn from_gray_srgb(g: u8) -> Self {
|
||||||
Self::from_srgb(g, g, g)
|
Self::from_srgb(g, g, g)
|
||||||
}
|
}
|
||||||
|
|
@ -456,10 +464,16 @@ colors! {
|
||||||
highlight = (0x9d, 0x28, 0xc6, 0x7f),
|
highlight = (0x9d, 0x28, 0xc6, 0x7f),
|
||||||
tab_active_background = (0x4c, 0x78, 0x99),
|
tab_active_background = (0x4c, 0x78, 0x99),
|
||||||
tab_active_border = (0x28, 0x55, 0x77),
|
tab_active_border = (0x28, 0x55, 0x77),
|
||||||
|
tab_focused_background = (0x3a, 0x5a, 0x70),
|
||||||
|
tab_focused_border = (0x28, 0x55, 0x77),
|
||||||
tab_inactive_background = (0x22, 0x22, 0x22),
|
tab_inactive_background = (0x22, 0x22, 0x22),
|
||||||
tab_inactive_border = (0x33, 0x33, 0x33),
|
tab_inactive_border = (0x33, 0x33, 0x33),
|
||||||
|
tab_urgent_background = (0x23, 0x09, 0x2c),
|
||||||
|
tab_urgent_border = (0x44, 0x11, 0x55),
|
||||||
tab_active_text = (0xff, 0xff, 0xff),
|
tab_active_text = (0xff, 0xff, 0xff),
|
||||||
|
tab_focused_text = (0xdd, 0xdd, 0xdd),
|
||||||
tab_inactive_text = (0x88, 0x88, 0x88),
|
tab_inactive_text = (0x88, 0x88, 0x88),
|
||||||
|
tab_urgent_text = (0xff, 0xff, 0xff),
|
||||||
tab_bar_background = (0x00, 0x00, 0x00, 0x00),
|
tab_bar_background = (0x00, 0x00, 0x00, 0x00),
|
||||||
tab_attention_background = (0x23, 0x09, 0x2c),
|
tab_attention_background = (0x23, 0x09, 0x2c),
|
||||||
}
|
}
|
||||||
|
|
@ -486,10 +500,16 @@ impl StaticText for ThemeColor {
|
||||||
ThemeColor::highlight => "Highlight",
|
ThemeColor::highlight => "Highlight",
|
||||||
ThemeColor::tab_active_background => "Tab Background (active)",
|
ThemeColor::tab_active_background => "Tab Background (active)",
|
||||||
ThemeColor::tab_active_border => "Tab Border (active)",
|
ThemeColor::tab_active_border => "Tab Border (active)",
|
||||||
|
ThemeColor::tab_focused_background => "Tab Background (focused)",
|
||||||
|
ThemeColor::tab_focused_border => "Tab Border (focused)",
|
||||||
ThemeColor::tab_inactive_background => "Tab Background (inactive)",
|
ThemeColor::tab_inactive_background => "Tab Background (inactive)",
|
||||||
ThemeColor::tab_inactive_border => "Tab Border (inactive)",
|
ThemeColor::tab_inactive_border => "Tab Border (inactive)",
|
||||||
|
ThemeColor::tab_urgent_background => "Tab Background (urgent)",
|
||||||
|
ThemeColor::tab_urgent_border => "Tab Border (urgent)",
|
||||||
ThemeColor::tab_active_text => "Tab Text (active)",
|
ThemeColor::tab_active_text => "Tab Text (active)",
|
||||||
|
ThemeColor::tab_focused_text => "Tab Text (focused)",
|
||||||
ThemeColor::tab_inactive_text => "Tab Text (inactive)",
|
ThemeColor::tab_inactive_text => "Tab Text (inactive)",
|
||||||
|
ThemeColor::tab_urgent_text => "Tab Text (urgent)",
|
||||||
ThemeColor::tab_bar_background => "Tab Bar Background",
|
ThemeColor::tab_bar_background => "Tab Bar Background",
|
||||||
ThemeColor::tab_attention_background => "Tab Attention Background",
|
ThemeColor::tab_attention_background => "Tab Attention Background",
|
||||||
}
|
}
|
||||||
|
|
@ -610,6 +630,7 @@ sizes! {
|
||||||
tab_bar_border_width = (0, 1000, 2),
|
tab_bar_border_width = (0, 1000, 2),
|
||||||
tab_bar_text_padding = (0, 1000, 4),
|
tab_bar_text_padding = (0, 1000, 4),
|
||||||
tab_bar_gap = (0, 1000, 4),
|
tab_bar_gap = (0, 1000, 4),
|
||||||
|
tab_opacity = (0, 100, 100),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StaticText for ThemeSized {
|
impl StaticText for ThemeSized {
|
||||||
|
|
@ -627,6 +648,7 @@ impl StaticText for ThemeSized {
|
||||||
ThemeSized::tab_bar_border_width => "Tab Bar Border Width",
|
ThemeSized::tab_bar_border_width => "Tab Bar Border Width",
|
||||||
ThemeSized::tab_bar_text_padding => "Tab Bar Text Padding",
|
ThemeSized::tab_bar_text_padding => "Tab Bar Text Padding",
|
||||||
ThemeSized::tab_bar_gap => "Tab Bar Gap",
|
ThemeSized::tab_bar_gap => "Tab Bar Gap",
|
||||||
|
ThemeSized::tab_opacity => "Tab Opacity",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -784,6 +806,8 @@ pub struct Theme {
|
||||||
pub corner_radius: Cell<CornerRadius>,
|
pub corner_radius: Cell<CornerRadius>,
|
||||||
pub autotile_enabled: Cell<bool>,
|
pub autotile_enabled: Cell<bool>,
|
||||||
pub tab_title_align: Cell<TabTitleAlign>,
|
pub tab_title_align: Cell<TabTitleAlign>,
|
||||||
|
pub tab_from_top: Cell<bool>,
|
||||||
|
pub tab_render_text: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Theme {
|
impl Default for Theme {
|
||||||
|
|
@ -802,6 +826,8 @@ impl Default for Theme {
|
||||||
corner_radius: Cell::new(CornerRadius::default()),
|
corner_radius: Cell::new(CornerRadius::default()),
|
||||||
autotile_enabled: Cell::new(false),
|
autotile_enabled: Cell::new(false),
|
||||||
tab_title_align: Cell::new(TabTitleAlign::default()),
|
tab_title_align: Cell::new(TabTitleAlign::default()),
|
||||||
|
tab_from_top: Cell::new(false),
|
||||||
|
tab_render_text: Cell::new(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,7 @@ use {
|
||||||
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FloatNode, FoundNode, Node,
|
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FloatNode, FoundNode, Node,
|
||||||
NodeId, NodeLayerLink, NodeLocation, OutputNode, TddType, TileDragDestination,
|
NodeId, NodeLayerLink, NodeLocation, OutputNode, TddType, TileDragDestination,
|
||||||
ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelType, WorkspaceNode,
|
ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelType, WorkspaceNode,
|
||||||
default_tile_drag_bounds,
|
default_tile_drag_bounds, tab_bar::TabBar, toplevel_set_workspace, walker::NodeVisitor,
|
||||||
tab_bar::{TabBar, TabBarEntry},
|
|
||||||
toplevel_set_workspace,
|
|
||||||
walker::NodeVisitor,
|
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::CloneCell,
|
clonecell::CloneCell,
|
||||||
|
|
@ -265,6 +262,9 @@ impl ContainerNode {
|
||||||
update_tab_textures_scheduled: Cell::new(false),
|
update_tab_textures_scheduled: Cell::new(false),
|
||||||
ephemeral: Cell::new(Ephemeral::Off),
|
ephemeral: Cell::new(Ephemeral::Off),
|
||||||
});
|
});
|
||||||
|
child_node_ref
|
||||||
|
.focus_history
|
||||||
|
.set(Some(slf.focus_history.add_last(child_node_ref.clone())));
|
||||||
child.tl_set_parent(slf.clone());
|
child.tl_set_parent(slf.clone());
|
||||||
slf.pull_child_properties(&child_node_ref);
|
slf.pull_child_properties(&child_node_ref);
|
||||||
slf
|
slf
|
||||||
|
|
@ -700,6 +700,32 @@ impl ContainerNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn register_tab_animation(self: &Rc<Self>) {
|
||||||
|
let mut list = self.state.tab_animation_containers.borrow_mut();
|
||||||
|
let already = list
|
||||||
|
.iter()
|
||||||
|
.any(|w| w.upgrade().map_or(false, |c| Rc::ptr_eq(&c, self)));
|
||||||
|
if !already {
|
||||||
|
list.push(Rc::downgrade(self));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick_tab_animations(&self) -> bool {
|
||||||
|
let now = self.state.now_nsec();
|
||||||
|
let cfg = self.state.animations_config.get();
|
||||||
|
let duration = if cfg.enabled { cfg.open_duration_ms } else { 0 };
|
||||||
|
let mut bar = self.tab_bar.borrow_mut();
|
||||||
|
if let Some(bar) = bar.as_mut() {
|
||||||
|
let animating = bar.tick(now, duration);
|
||||||
|
if animating {
|
||||||
|
self.damage();
|
||||||
|
}
|
||||||
|
animating
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_render_positions(&self) {
|
fn compute_render_positions(&self) {
|
||||||
self.compute_render_positions_scheduled.set(false);
|
self.compute_render_positions_scheduled.set(false);
|
||||||
let mut rd = self.render_data.borrow_mut();
|
let mut rd = self.render_data.borrow_mut();
|
||||||
|
|
@ -780,6 +806,7 @@ impl ContainerNode {
|
||||||
if self.mono_child.is_some() == child.is_some() {
|
if self.mono_child.is_some() == child.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let exiting_mono = self.mono_child.is_some() && child.is_none();
|
||||||
let child = {
|
let child = {
|
||||||
let children = self.child_nodes.borrow();
|
let children = self.child_nodes.borrow();
|
||||||
match child {
|
match child {
|
||||||
|
|
@ -813,6 +840,9 @@ impl ContainerNode {
|
||||||
} else {
|
} else {
|
||||||
for child in self.children.iter() {
|
for child in self.children.iter() {
|
||||||
child.node.tl_set_visible(true);
|
child.node.tl_set_visible(true);
|
||||||
|
if exiting_mono {
|
||||||
|
child.node.tl_data().start_open_animation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -913,9 +943,10 @@ impl ContainerNode {
|
||||||
"untitled".to_string()
|
"untitled".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rebuild the tab bar. If `override_id` and `override_title` are provided,
|
/// Update the tab bar entries to match the current children. Preserves
|
||||||
/// use the override title for that child instead of borrowing it (avoids
|
/// existing entries (and their animation state) for children still present,
|
||||||
/// RefCell double-borrow when called from node_child_title_changed).
|
/// starts exit animations for removed children, and creates new entries
|
||||||
|
/// with enter animations for added children.
|
||||||
fn rebuild_tab_bar_with_override(
|
fn rebuild_tab_bar_with_override(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
override_id: Option<NodeId>,
|
override_id: Option<NodeId>,
|
||||||
|
|
@ -938,24 +969,35 @@ impl ContainerNode {
|
||||||
.persistent
|
.persistent
|
||||||
.scale
|
.scale
|
||||||
.get();
|
.get();
|
||||||
let mut bar = TabBar::new(height, render_scale);
|
let now = self.state.now_nsec();
|
||||||
for child in self.children.iter() {
|
|
||||||
|
let children_data: Vec<_> = self
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.map(|child| {
|
||||||
let child_id = child.node.node_id();
|
let child_id = child.node.node_id();
|
||||||
let title = self.get_child_tab_title(&child, override_id, override_title);
|
let title = self.get_child_tab_title(&child, override_id, override_title);
|
||||||
bar.entries.push(TabBarEntry {
|
let active = child_id == active_id;
|
||||||
child_id,
|
let focused = active && child.active.get();
|
||||||
title,
|
let urgent = child.attention_requested.get();
|
||||||
title_texture: Rc::new(RefCell::new(None)),
|
(child_id, title, active, focused, urgent)
|
||||||
active: child_id == active_id,
|
})
|
||||||
attention_requested: child.attention_requested.get(),
|
.collect();
|
||||||
x: Cell::new(0),
|
|
||||||
width: Cell::new(0),
|
let mut bar_ref = self.tab_bar.borrow_mut();
|
||||||
});
|
let bar = bar_ref.get_or_insert_with(|| TabBar::new(height, render_scale));
|
||||||
}
|
bar.height = height;
|
||||||
let padding = self.state.theme.sizes.tab_bar_padding.get();
|
bar.render_scale = render_scale;
|
||||||
bar.layout_entries(self.width.get(), padding);
|
bar.update_entries(&children_data, now);
|
||||||
*self.tab_bar.borrow_mut() = Some(bar);
|
bar.update_animations();
|
||||||
|
let animating = bar.entries.iter().any(|e| e.is_animating());
|
||||||
|
|
||||||
|
drop(bar_ref);
|
||||||
self.schedule_update_tab_textures();
|
self.schedule_update_tab_textures();
|
||||||
|
|
||||||
|
if animating {
|
||||||
|
self.register_tab_animation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrap the focused child in a new sub-container with the given split direction.
|
/// Wrap the focused child in a new sub-container with the given split direction.
|
||||||
|
|
@ -1421,8 +1463,11 @@ impl ContainerNode {
|
||||||
let tab_bar = self.tab_bar.borrow();
|
let tab_bar = self.tab_bar.borrow();
|
||||||
if let Some(tb) = tab_bar.as_ref() {
|
if let Some(tb) = tab_bar.as_ref() {
|
||||||
if seat_data.y >= 0 && seat_data.y < tb.height {
|
if seat_data.y >= 0 && seat_data.y < tb.height {
|
||||||
if let Some(idx) = tb.entry_at_x(seat_data.x) {
|
let padding = self.state.theme.sizes.tab_bar_padding.get();
|
||||||
let child_id = tb.entries[idx].child_id;
|
if let Some(idx) = tb.entry_at_x(seat_data.x, self.width.get(), padding) {
|
||||||
|
let active_entries: Vec<_> =
|
||||||
|
tb.entries.iter().filter(|e| !e.destroying).collect();
|
||||||
|
let child_id = active_entries[idx].child_id;
|
||||||
drop(tab_bar);
|
drop(tab_bar);
|
||||||
drop(seat_datas);
|
drop(seat_datas);
|
||||||
let children = self.child_nodes.borrow();
|
let children = self.child_nodes.borrow();
|
||||||
|
|
@ -1698,6 +1743,7 @@ impl ContainerNode {
|
||||||
let entries: Vec<_> = tb
|
let entries: Vec<_> = tb
|
||||||
.entries
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|e| !e.destroying)
|
||||||
.map(|e| {
|
.map(|e| {
|
||||||
(
|
(
|
||||||
e.title.clone(),
|
e.title.clone(),
|
||||||
|
|
@ -1722,7 +1768,6 @@ impl ContainerNode {
|
||||||
if let Some(s) = scale {
|
if let Some(s) = scale {
|
||||||
texture_height = (bar_height as f64 * s).round() as _;
|
texture_height = (bar_height as f64 * s).round() as _;
|
||||||
}
|
}
|
||||||
let mut scheduled = 0;
|
|
||||||
let mut texture_refs = Vec::new();
|
let mut texture_refs = Vec::new();
|
||||||
for (title, (_, _, text_color), title_texture) in &entries {
|
for (title, (_, _, text_color), title_texture) in &entries {
|
||||||
let mut tex_ref = title_texture.borrow_mut();
|
let mut tex_ref = title_texture.borrow_mut();
|
||||||
|
|
@ -1737,7 +1782,6 @@ impl ContainerNode {
|
||||||
scale,
|
scale,
|
||||||
);
|
);
|
||||||
texture_refs.push(title_texture.clone());
|
texture_refs.push(title_texture.clone());
|
||||||
scheduled += 1;
|
|
||||||
}
|
}
|
||||||
(on_completed.event(), texture_refs)
|
(on_completed.event(), texture_refs)
|
||||||
}
|
}
|
||||||
|
|
@ -2404,11 +2448,9 @@ impl ToplevelNodeBase for ContainerNode {
|
||||||
size_changed |= self.height.replace(rect.height()) != rect.height();
|
size_changed |= self.height.replace(rect.height()) != rect.height();
|
||||||
if size_changed {
|
if size_changed {
|
||||||
self.update_content_size();
|
self.update_content_size();
|
||||||
// Re-layout tab bar entries when container size changes in mono mode.
|
|
||||||
if self.mono_child.is_some() {
|
if self.mono_child.is_some() {
|
||||||
if let Some(bar) = self.tab_bar.borrow().as_ref() {
|
if let Some(bar) = self.tab_bar.borrow_mut().as_mut() {
|
||||||
let padding = self.state.theme.sizes.tab_bar_padding.get();
|
bar.update_animations();
|
||||||
bar.layout_entries(rect.width(), padding);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// log::info!("tl_change_extents");
|
// log::info!("tl_change_extents");
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,85 @@
|
||||||
use {
|
use {
|
||||||
crate::{scale::Scale, state::State, text::TextTexture, theme::Color, tree::NodeId},
|
crate::{
|
||||||
std::{
|
scale::Scale,
|
||||||
cell::{Cell, RefCell},
|
state::State,
|
||||||
rc::Rc,
|
text::TextTexture,
|
||||||
|
theme::{Color, Oklab},
|
||||||
|
tree::NodeId,
|
||||||
},
|
},
|
||||||
|
std::{cell::RefCell, rc::Rc},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A single animated float value that interpolates from `start` to `target`
|
||||||
|
/// over a configurable duration using an ease-out cubic curve.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AnimatedF32 {
|
||||||
|
start: f32,
|
||||||
|
target: f32,
|
||||||
|
current: f32,
|
||||||
|
start_nsec: u64,
|
||||||
|
warped: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimatedF32 {
|
||||||
|
pub fn new(value: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
start: value,
|
||||||
|
target: value,
|
||||||
|
current: value,
|
||||||
|
start_nsec: 0,
|
||||||
|
warped: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, target: f32, now_nsec: u64) {
|
||||||
|
if (self.target - target).abs() < 1e-6 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.start = self.current;
|
||||||
|
self.target = target;
|
||||||
|
self.start_nsec = now_nsec;
|
||||||
|
self.warped = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warp(&mut self, value: f32) {
|
||||||
|
self.start = value;
|
||||||
|
self.target = value;
|
||||||
|
self.current = value;
|
||||||
|
self.warped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(&self) -> f32 {
|
||||||
|
self.current
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target(&self) -> f32 {
|
||||||
|
self.target
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_animating(&self) -> bool {
|
||||||
|
!self.warped && (self.current - self.target).abs() > 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self, now_nsec: u64, duration_ms: u32) {
|
||||||
|
if self.warped || duration_ms == 0 {
|
||||||
|
self.current = self.target;
|
||||||
|
self.warped = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let elapsed = now_nsec.saturating_sub(self.start_nsec);
|
||||||
|
let dur = (duration_ms as u64).saturating_mul(1_000_000);
|
||||||
|
if elapsed >= dur {
|
||||||
|
self.current = self.target;
|
||||||
|
self.warped = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let t = (elapsed as f32) / (dur as f32);
|
||||||
|
let inv = 1.0 - t;
|
||||||
|
let eased = 1.0 - inv * inv * inv;
|
||||||
|
self.current = self.start + (self.target - self.start) * eased;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A single entry (tab) within a tab bar.
|
/// A single entry (tab) within a tab bar.
|
||||||
pub struct TabBarEntry {
|
pub struct TabBarEntry {
|
||||||
/// The node ID of the child this tab represents.
|
/// The node ID of the child this tab represents.
|
||||||
|
|
@ -14,19 +88,185 @@ pub struct TabBarEntry {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
/// Pre-rendered text texture for the tab title.
|
/// Pre-rendered text texture for the tab title.
|
||||||
pub title_texture: Rc<RefCell<Option<TextTexture>>>,
|
pub title_texture: Rc<RefCell<Option<TextTexture>>>,
|
||||||
/// Whether this is the active (visible) tab.
|
/// Whether this entry is being destroyed (exit animation in flight).
|
||||||
pub active: bool,
|
pub destroying: bool,
|
||||||
/// Whether this tab's window has requested attention.
|
|
||||||
pub attention_requested: bool,
|
/// Horizontal offset as a fraction of total bar width (0.0–1.0).
|
||||||
/// X offset of this tab within the tab bar (relative to tab bar start).
|
pub offset: AnimatedF32,
|
||||||
pub x: Cell<i32>,
|
/// Width as a fraction of total bar width (0.0–1.0).
|
||||||
/// Width of this tab in pixels.
|
pub width: AnimatedF32,
|
||||||
pub width: Cell<i32>,
|
/// Vertical slide position: 0.0 = in place, 1.0 = fully slid out.
|
||||||
|
pub vertical_pos: AnimatedF32,
|
||||||
|
/// Fade opacity: 0.0 = invisible, 1.0 = fully visible.
|
||||||
|
pub fade_opacity: AnimatedF32,
|
||||||
|
/// Active (selected tab) blend weight: 0.0–1.0.
|
||||||
|
pub active_anim: AnimatedF32,
|
||||||
|
/// Focused (keyboard focus on this tab's container) blend weight: 0.0–1.0.
|
||||||
|
pub focused_anim: AnimatedF32,
|
||||||
|
/// Urgent/attention blend weight: 0.0–1.0.
|
||||||
|
pub urgent_anim: AnimatedF32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabBarEntry {
|
||||||
|
pub fn new(
|
||||||
|
child_id: NodeId,
|
||||||
|
title: String,
|
||||||
|
active: bool,
|
||||||
|
focused: bool,
|
||||||
|
urgent: bool,
|
||||||
|
now_nsec: u64,
|
||||||
|
) -> Self {
|
||||||
|
let mut entry = Self {
|
||||||
|
child_id,
|
||||||
|
title,
|
||||||
|
title_texture: Rc::new(RefCell::new(None)),
|
||||||
|
destroying: false,
|
||||||
|
offset: AnimatedF32::new(-1.0),
|
||||||
|
width: AnimatedF32::new(-1.0),
|
||||||
|
vertical_pos: AnimatedF32::new(1.0),
|
||||||
|
fade_opacity: AnimatedF32::new(0.0),
|
||||||
|
active_anim: AnimatedF32::new(if active { 1.0 } else { 0.0 }),
|
||||||
|
focused_anim: AnimatedF32::new(if focused { 1.0 } else { 0.0 }),
|
||||||
|
urgent_anim: AnimatedF32::new(if urgent { 1.0 } else { 0.0 }),
|
||||||
|
};
|
||||||
|
entry.vertical_pos.set(0.0, now_nsec);
|
||||||
|
entry.fade_opacity.set(1.0, now_nsec);
|
||||||
|
entry
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn begin_destroy(&mut self, now_nsec: u64) {
|
||||||
|
self.destroying = true;
|
||||||
|
self.vertical_pos.set(1.0, now_nsec);
|
||||||
|
self.fade_opacity.set(0.0, now_nsec);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn un_destroy(&mut self, now_nsec: u64) {
|
||||||
|
self.destroying = false;
|
||||||
|
self.vertical_pos.set(0.0, now_nsec);
|
||||||
|
self.fade_opacity.set(1.0, now_nsec);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_remove(&self) -> bool {
|
||||||
|
self.destroying && (!self.vertical_pos.is_animating() || self.width.value() < 1e-4)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_active(&mut self, active: bool, _now_nsec: u64) {
|
||||||
|
let target = if active { 1.0 } else { 0.0 };
|
||||||
|
self.active_anim.warp(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_focused(&mut self, focused: bool, _now_nsec: u64) {
|
||||||
|
let target = if focused { 1.0 } else { 0.0 };
|
||||||
|
self.focused_anim.warp(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_urgent(&mut self, urgent: bool, _now_nsec: u64) {
|
||||||
|
let u = if urgent && self.focused_anim.target() < 0.5 {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
self.urgent_anim.warp(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_title(&mut self, title: String) {
|
||||||
|
if self.title != title {
|
||||||
|
self.title = title;
|
||||||
|
*self.title_texture.borrow_mut() = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick_all(&mut self, now_nsec: u64, duration_ms: u32) {
|
||||||
|
self.offset.tick(now_nsec, duration_ms);
|
||||||
|
self.width.tick(now_nsec, duration_ms);
|
||||||
|
self.vertical_pos.tick(now_nsec, duration_ms);
|
||||||
|
self.fade_opacity.tick(now_nsec, duration_ms);
|
||||||
|
self.active_anim.tick(now_nsec, duration_ms);
|
||||||
|
self.focused_anim.tick(now_nsec, duration_ms);
|
||||||
|
self.urgent_anim.tick(now_nsec, duration_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_animating(&self) -> bool {
|
||||||
|
self.offset.is_animating()
|
||||||
|
|| self.width.is_animating()
|
||||||
|
|| self.vertical_pos.is_animating()
|
||||||
|
|| self.fade_opacity.is_animating()
|
||||||
|
|| self.active_anim.is_animating()
|
||||||
|
|| self.focused_anim.is_animating()
|
||||||
|
|| self.urgent_anim.is_animating()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the blended color for this entry, interpolating in OkLab space
|
||||||
|
/// between active, focused, urgent, and inactive colors based on the
|
||||||
|
/// animated blend weights.
|
||||||
|
pub fn blended_colors(&self, state: &State) -> (Color, Color, Color) {
|
||||||
|
let theme = &state.theme;
|
||||||
|
|
||||||
|
let active_v = self.active_anim.value();
|
||||||
|
let urgent_v = (self.urgent_anim.value() - active_v).max(0.0);
|
||||||
|
let focused_v = (self.focused_anim.value() - active_v - urgent_v).max(0.0);
|
||||||
|
let inactive_v = (1.0 - active_v - urgent_v - focused_v).max(0.0);
|
||||||
|
|
||||||
|
let bg = blend_colors_oklab(&[
|
||||||
|
(active_v, theme.colors.tab_active_background.get()),
|
||||||
|
(focused_v, theme.colors.tab_focused_background.get()),
|
||||||
|
(urgent_v, theme.colors.tab_urgent_background.get()),
|
||||||
|
(inactive_v, theme.colors.tab_inactive_background.get()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let border = blend_colors_oklab(&[
|
||||||
|
(active_v, theme.colors.tab_active_border.get()),
|
||||||
|
(focused_v, theme.colors.tab_focused_border.get()),
|
||||||
|
(urgent_v, theme.colors.tab_urgent_border.get()),
|
||||||
|
(inactive_v, theme.colors.tab_inactive_border.get()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let text = blend_colors_oklab(&[
|
||||||
|
(active_v, theme.colors.tab_active_text.get()),
|
||||||
|
(focused_v, theme.colors.tab_focused_text.get()),
|
||||||
|
(urgent_v, theme.colors.tab_urgent_text.get()),
|
||||||
|
(inactive_v, theme.colors.tab_inactive_text.get()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
(bg, border, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blend_colors_oklab(weighted: &[(f32, Color)]) -> Color {
|
||||||
|
let mut total_weight = 0.0f32;
|
||||||
|
let mut lab = Oklab {
|
||||||
|
l: 0.0,
|
||||||
|
a: 0.0,
|
||||||
|
b: 0.0,
|
||||||
|
};
|
||||||
|
let mut alpha = 0.0f32;
|
||||||
|
for &(w, color) in weighted {
|
||||||
|
if w < 1e-6 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
total_weight += w;
|
||||||
|
let c_lab = color.srgb_to_oklab();
|
||||||
|
lab.l += w * c_lab.l;
|
||||||
|
lab.a += w * c_lab.a;
|
||||||
|
lab.b += w * c_lab.b;
|
||||||
|
alpha += w * color.alpha();
|
||||||
|
}
|
||||||
|
if total_weight < 1e-6 {
|
||||||
|
return Color::TRANSPARENT;
|
||||||
|
}
|
||||||
|
let inv = 1.0 / total_weight;
|
||||||
|
lab.l *= inv;
|
||||||
|
lab.a *= inv;
|
||||||
|
lab.b *= inv;
|
||||||
|
alpha *= inv;
|
||||||
|
let mut c = lab.to_srgb();
|
||||||
|
c.set_alpha(alpha);
|
||||||
|
c
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A tab bar rendered above a container in mono (tabbed) mode.
|
/// A tab bar rendered above a container in mono (tabbed) mode.
|
||||||
pub struct TabBar {
|
pub struct TabBar {
|
||||||
/// The individual tab entries.
|
/// The individual tab entries (persistent across updates).
|
||||||
pub entries: Vec<TabBarEntry>,
|
pub entries: Vec<TabBarEntry>,
|
||||||
/// Height of the tab bar in pixels (from theme).
|
/// Height of the tab bar in pixels (from theme).
|
||||||
pub height: i32,
|
pub height: i32,
|
||||||
|
|
@ -35,7 +275,6 @@ pub struct TabBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TabBar {
|
impl TabBar {
|
||||||
/// Create a new empty tab bar.
|
|
||||||
pub fn new(height: i32, render_scale: Scale) -> Self {
|
pub fn new(height: i32, render_scale: Scale) -> Self {
|
||||||
Self {
|
Self {
|
||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
|
|
@ -44,67 +283,116 @@ impl TabBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recompute the positions and widths of all tab entries.
|
/// Update entries to match the current set of children. Preserves existing
|
||||||
///
|
/// entries (and their animation state) for children that are still present,
|
||||||
/// `total_width` is the available width for the entire tab bar.
|
/// starts exit animations for removed children, and creates new entries with
|
||||||
pub fn layout_entries(&self, total_width: i32, padding: i32) {
|
/// enter animations for added children.
|
||||||
let n = self.entries.len() as i32;
|
pub fn update_entries(
|
||||||
if n == 0 {
|
&mut self,
|
||||||
|
children: &[(NodeId, String, bool, bool, bool)],
|
||||||
|
now_nsec: u64,
|
||||||
|
) {
|
||||||
|
let mut old_entries: Vec<TabBarEntry> = self.entries.drain(..).collect();
|
||||||
|
|
||||||
|
for &(child_id, ref title, active, focused, urgent) in children {
|
||||||
|
if let Some(pos) = old_entries.iter().position(|e| e.child_id == child_id) {
|
||||||
|
let mut entry = old_entries.remove(pos);
|
||||||
|
if entry.destroying {
|
||||||
|
entry.un_destroy(now_nsec);
|
||||||
|
}
|
||||||
|
entry.set_active(active, now_nsec);
|
||||||
|
entry.set_focused(focused, now_nsec);
|
||||||
|
entry.set_urgent(urgent, now_nsec);
|
||||||
|
entry.set_title(title.clone());
|
||||||
|
self.entries.push(entry);
|
||||||
|
} else {
|
||||||
|
let mut entry =
|
||||||
|
TabBarEntry::new(child_id, title.clone(), active, focused, urgent, now_nsec);
|
||||||
|
entry.vertical_pos.warp(0.0);
|
||||||
|
entry.fade_opacity.warp(1.0);
|
||||||
|
self.entries.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for mut entry in old_entries {
|
||||||
|
if !entry.destroying {
|
||||||
|
entry.begin_destroy(now_nsec);
|
||||||
|
}
|
||||||
|
if entry.should_remove() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.entries.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recompute offset/width for all entries. All values are warped
|
||||||
|
/// to their final positions immediately (no animation on position).
|
||||||
|
pub fn update_animations(&mut self) {
|
||||||
|
let active_count = self.entries.iter().filter(|e| !e.destroying).count();
|
||||||
|
if active_count == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let entry_width = 1.0 / active_count as f32;
|
||||||
|
let mut offset = 0.0f32;
|
||||||
|
|
||||||
|
for entry in &mut self.entries {
|
||||||
|
if !entry.destroying {
|
||||||
|
entry.offset.warp(offset);
|
||||||
|
entry.width.warp(entry_width);
|
||||||
|
offset += entry_width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tick all animations forward and remove completed exit animations.
|
||||||
|
/// Returns true if any animation is still in flight.
|
||||||
|
pub fn tick(&mut self, now_nsec: u64, duration_ms: u32) -> bool {
|
||||||
|
for entry in &mut self.entries {
|
||||||
|
entry.tick_all(now_nsec, duration_ms);
|
||||||
|
}
|
||||||
|
self.entries.retain(|e| !e.should_remove());
|
||||||
|
self.is_animating()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_animating(&self) -> bool {
|
||||||
|
self.entries.iter().any(|e| e.is_animating())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the tab entry index at the given x coordinate (relative to tab bar).
|
||||||
|
/// Only considers non-destroying entries.
|
||||||
|
pub fn entry_at_x(&self, x: i32, total_width: i32, padding: i32) -> Option<usize> {
|
||||||
|
let active_entries: Vec<_> = self
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, e)| !e.destroying)
|
||||||
|
.collect();
|
||||||
|
let n = active_entries.len() as i32;
|
||||||
|
if n == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let total_padding = padding * (n + 1);
|
let total_padding = padding * (n + 1);
|
||||||
let available = (total_width - total_padding).max(0);
|
let available = (total_width - total_padding).max(0);
|
||||||
let per_tab = available / n;
|
let per_tab = available / n;
|
||||||
let mut remainder = available - per_tab * n;
|
let mut remainder = available - per_tab * n;
|
||||||
let mut x = padding;
|
let mut tab_x = padding;
|
||||||
for entry in &self.entries {
|
for (i, (_idx, _entry)) in active_entries.iter().enumerate() {
|
||||||
let w = if remainder > 0 {
|
let w = if remainder > 0 {
|
||||||
remainder -= 1;
|
remainder -= 1;
|
||||||
per_tab + 1
|
per_tab + 1
|
||||||
} else {
|
} else {
|
||||||
per_tab
|
per_tab
|
||||||
};
|
};
|
||||||
entry.x.set(x);
|
if x >= tab_x && x < tab_x + w {
|
||||||
entry.width.set(w);
|
|
||||||
x += w + padding;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the tab entry index at the given x coordinate (relative to tab bar).
|
|
||||||
///
|
|
||||||
/// Returns `None` if the coordinate is in padding between tabs or out of bounds.
|
|
||||||
pub fn entry_at_x(&self, x: i32) -> Option<usize> {
|
|
||||||
for (i, entry) in self.entries.iter().enumerate() {
|
|
||||||
let ex = entry.x.get();
|
|
||||||
let ew = entry.width.get();
|
|
||||||
if x >= ex && x < ex + ew {
|
|
||||||
return Some(i);
|
return Some(i);
|
||||||
}
|
}
|
||||||
|
tab_x += w + padding;
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the colors for a tab entry based on its state.
|
/// Get bg, border, text colors.
|
||||||
pub fn entry_colors(state: &State, entry: &TabBarEntry) -> (Color, Color, Color) {
|
pub fn entry_colors(state: &State, entry: &TabBarEntry) -> (Color, Color, Color) {
|
||||||
let theme = &state.theme;
|
entry.blended_colors(state)
|
||||||
if entry.attention_requested {
|
|
||||||
(
|
|
||||||
theme.colors.tab_attention_background.get(),
|
|
||||||
theme.colors.tab_inactive_border.get(),
|
|
||||||
theme.colors.tab_active_text.get(),
|
|
||||||
)
|
|
||||||
} else if entry.active {
|
|
||||||
(
|
|
||||||
theme.colors.tab_active_background.get(),
|
|
||||||
theme.colors.tab_active_border.get(),
|
|
||||||
theme.colors.tab_active_text.get(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
theme.colors.tab_inactive_background.get(),
|
|
||||||
theme.colors.tab_inactive_border.get(),
|
|
||||||
theme.colors.tab_inactive_text.get(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -241,10 +241,16 @@ pub struct Theme {
|
||||||
pub corner_radius: Option<f32>,
|
pub corner_radius: Option<f32>,
|
||||||
pub tab_active_bg_color: Option<Color>,
|
pub tab_active_bg_color: Option<Color>,
|
||||||
pub tab_active_border_color: Option<Color>,
|
pub tab_active_border_color: Option<Color>,
|
||||||
|
pub tab_focused_bg_color: Option<Color>,
|
||||||
|
pub tab_focused_border_color: Option<Color>,
|
||||||
pub tab_inactive_bg_color: Option<Color>,
|
pub tab_inactive_bg_color: Option<Color>,
|
||||||
pub tab_inactive_border_color: Option<Color>,
|
pub tab_inactive_border_color: Option<Color>,
|
||||||
|
pub tab_urgent_bg_color: Option<Color>,
|
||||||
|
pub tab_urgent_border_color: Option<Color>,
|
||||||
pub tab_active_text_color: Option<Color>,
|
pub tab_active_text_color: Option<Color>,
|
||||||
|
pub tab_focused_text_color: Option<Color>,
|
||||||
pub tab_inactive_text_color: Option<Color>,
|
pub tab_inactive_text_color: Option<Color>,
|
||||||
|
pub tab_urgent_text_color: Option<Color>,
|
||||||
pub tab_bar_bg_color: Option<Color>,
|
pub tab_bar_bg_color: Option<Color>,
|
||||||
pub tab_attention_bg_color: Option<Color>,
|
pub tab_attention_bg_color: Option<Color>,
|
||||||
pub tab_bar_height: Option<i32>,
|
pub tab_bar_height: Option<i32>,
|
||||||
|
|
@ -253,7 +259,10 @@ pub struct Theme {
|
||||||
pub tab_bar_border_width: Option<i32>,
|
pub tab_bar_border_width: Option<i32>,
|
||||||
pub tab_bar_text_padding: Option<i32>,
|
pub tab_bar_text_padding: Option<i32>,
|
||||||
pub tab_bar_gap: Option<i32>,
|
pub tab_bar_gap: Option<i32>,
|
||||||
|
pub tab_opacity: Option<i32>,
|
||||||
pub tab_title_align: Option<String>,
|
pub tab_title_align: Option<String>,
|
||||||
|
pub tab_from_top: Option<bool>,
|
||||||
|
pub tab_render_text: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -398,6 +407,7 @@ pub enum AnimationCurve {
|
||||||
Linear,
|
Linear,
|
||||||
EaseOut,
|
EaseOut,
|
||||||
EaseInOut,
|
EaseInOut,
|
||||||
|
#[allow(dead_code)]
|
||||||
Bezier { x1: f32, y1: f32, x2: f32, y2: f32 },
|
Bezier { x1: f32, y1: f32, x2: f32, y2: f32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,41 +105,58 @@ impl Parser for ThemeParser<'_> {
|
||||||
(
|
(
|
||||||
tab_active_bg_color,
|
tab_active_bg_color,
|
||||||
tab_active_border_color,
|
tab_active_border_color,
|
||||||
|
tab_focused_bg_color,
|
||||||
|
tab_focused_border_color,
|
||||||
tab_inactive_bg_color,
|
tab_inactive_bg_color,
|
||||||
tab_inactive_border_color,
|
tab_inactive_border_color,
|
||||||
|
tab_urgent_bg_color,
|
||||||
|
tab_urgent_border_color,
|
||||||
tab_active_text_color,
|
tab_active_text_color,
|
||||||
|
tab_focused_text_color,
|
||||||
|
),
|
||||||
|
(
|
||||||
tab_inactive_text_color,
|
tab_inactive_text_color,
|
||||||
|
tab_urgent_text_color,
|
||||||
tab_bar_bg_color,
|
tab_bar_bg_color,
|
||||||
tab_attention_bg_color,
|
tab_attention_bg_color,
|
||||||
tab_bar_height,
|
tab_bar_height,
|
||||||
tab_bar_padding,
|
tab_bar_padding,
|
||||||
),
|
|
||||||
(
|
|
||||||
tab_bar_radius,
|
tab_bar_radius,
|
||||||
tab_bar_border_width,
|
tab_bar_border_width,
|
||||||
tab_bar_text_padding,
|
tab_bar_text_padding,
|
||||||
tab_bar_gap,
|
tab_bar_gap,
|
||||||
tab_title_align_val,
|
|
||||||
),
|
),
|
||||||
|
(tab_opacity, tab_title_align_val, tab_from_top, tab_render_text),
|
||||||
) = ext.extract((
|
) = ext.extract((
|
||||||
(
|
(
|
||||||
opt(val("tab-active-bg-color")),
|
opt(val("tab-active-bg-color")),
|
||||||
opt(val("tab-active-border-color")),
|
opt(val("tab-active-border-color")),
|
||||||
|
opt(val("tab-focused-bg-color")),
|
||||||
|
opt(val("tab-focused-border-color")),
|
||||||
opt(val("tab-inactive-bg-color")),
|
opt(val("tab-inactive-bg-color")),
|
||||||
opt(val("tab-inactive-border-color")),
|
opt(val("tab-inactive-border-color")),
|
||||||
|
opt(val("tab-urgent-bg-color")),
|
||||||
|
opt(val("tab-urgent-border-color")),
|
||||||
opt(val("tab-active-text-color")),
|
opt(val("tab-active-text-color")),
|
||||||
|
opt(val("tab-focused-text-color")),
|
||||||
|
),
|
||||||
|
(
|
||||||
opt(val("tab-inactive-text-color")),
|
opt(val("tab-inactive-text-color")),
|
||||||
|
opt(val("tab-urgent-text-color")),
|
||||||
opt(val("tab-bar-bg-color")),
|
opt(val("tab-bar-bg-color")),
|
||||||
opt(val("tab-attention-bg-color")),
|
opt(val("tab-attention-bg-color")),
|
||||||
recover(opt(s32("tab-bar-height"))),
|
recover(opt(s32("tab-bar-height"))),
|
||||||
recover(opt(s32("tab-bar-padding"))),
|
recover(opt(s32("tab-bar-padding"))),
|
||||||
),
|
|
||||||
(
|
|
||||||
recover(opt(s32("tab-bar-radius"))),
|
recover(opt(s32("tab-bar-radius"))),
|
||||||
recover(opt(s32("tab-bar-border-width"))),
|
recover(opt(s32("tab-bar-border-width"))),
|
||||||
recover(opt(s32("tab-bar-text-padding"))),
|
recover(opt(s32("tab-bar-text-padding"))),
|
||||||
recover(opt(s32("tab-bar-gap"))),
|
recover(opt(s32("tab-bar-gap"))),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
recover(opt(s32("tab-opacity"))),
|
||||||
recover(opt(str("tab-title-align"))),
|
recover(opt(str("tab-title-align"))),
|
||||||
|
recover(opt(bol("tab-from-top"))),
|
||||||
|
recover(opt(bol("tab-render-text"))),
|
||||||
),
|
),
|
||||||
))?;
|
))?;
|
||||||
macro_rules! color {
|
macro_rules! color {
|
||||||
|
|
@ -199,10 +216,16 @@ impl Parser for ThemeParser<'_> {
|
||||||
corner_radius: corner_radius.map(|v| v.value as f32),
|
corner_radius: corner_radius.map(|v| v.value as f32),
|
||||||
tab_active_bg_color: color!(tab_active_bg_color),
|
tab_active_bg_color: color!(tab_active_bg_color),
|
||||||
tab_active_border_color: color!(tab_active_border_color),
|
tab_active_border_color: color!(tab_active_border_color),
|
||||||
|
tab_focused_bg_color: color!(tab_focused_bg_color),
|
||||||
|
tab_focused_border_color: color!(tab_focused_border_color),
|
||||||
tab_inactive_bg_color: color!(tab_inactive_bg_color),
|
tab_inactive_bg_color: color!(tab_inactive_bg_color),
|
||||||
tab_inactive_border_color: color!(tab_inactive_border_color),
|
tab_inactive_border_color: color!(tab_inactive_border_color),
|
||||||
|
tab_urgent_bg_color: color!(tab_urgent_bg_color),
|
||||||
|
tab_urgent_border_color: color!(tab_urgent_border_color),
|
||||||
tab_active_text_color: color!(tab_active_text_color),
|
tab_active_text_color: color!(tab_active_text_color),
|
||||||
|
tab_focused_text_color: color!(tab_focused_text_color),
|
||||||
tab_inactive_text_color: color!(tab_inactive_text_color),
|
tab_inactive_text_color: color!(tab_inactive_text_color),
|
||||||
|
tab_urgent_text_color: color!(tab_urgent_text_color),
|
||||||
tab_bar_bg_color: color!(tab_bar_bg_color),
|
tab_bar_bg_color: color!(tab_bar_bg_color),
|
||||||
tab_attention_bg_color: color!(tab_attention_bg_color),
|
tab_attention_bg_color: color!(tab_attention_bg_color),
|
||||||
tab_bar_height: tab_bar_height.despan(),
|
tab_bar_height: tab_bar_height.despan(),
|
||||||
|
|
@ -211,7 +234,10 @@ impl Parser for ThemeParser<'_> {
|
||||||
tab_bar_border_width: tab_bar_border_width.despan(),
|
tab_bar_border_width: tab_bar_border_width.despan(),
|
||||||
tab_bar_text_padding: tab_bar_text_padding.despan(),
|
tab_bar_text_padding: tab_bar_text_padding.despan(),
|
||||||
tab_bar_gap: tab_bar_gap.despan(),
|
tab_bar_gap: tab_bar_gap.despan(),
|
||||||
|
tab_opacity: tab_opacity.despan(),
|
||||||
tab_title_align: tab_title_align_val.map(|v| v.value.to_string()),
|
tab_title_align: tab_title_align_val.map(|v| v.value.to_string()),
|
||||||
|
tab_from_top: tab_from_top.despan(),
|
||||||
|
tab_render_text: tab_render_text.despan(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
config::{
|
config::{
|
||||||
Action, AnimationCurve, AnimationsConfig, BlurConfig, ClientRule, Config,
|
Action, AnimationCurve, AnimationsConfig, BlurConfig, ClientRule, Config,
|
||||||
ConfigConnector, ConfigDrmDevice, ConfigKeymap,
|
ConfigConnector, ConfigDrmDevice, ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec,
|
||||||
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, LayerKind, LayerRule, Output,
|
Input, InputMatch, LayerKind, LayerRule, Output, OutputMatch, SimpleCommand, Status,
|
||||||
OutputMatch, SimpleCommand, Status, Theme, WindowRule, parse_config,
|
Theme, WindowRule, parse_config,
|
||||||
},
|
},
|
||||||
rules::{MatcherTemp, RuleMapper},
|
rules::{MatcherTemp, RuleMapper},
|
||||||
shortcuts::ModeState,
|
shortcuts::ModeState,
|
||||||
|
|
@ -47,8 +47,8 @@ use {
|
||||||
set_color_management_enabled, set_corner_radius, set_default_workspace_capture,
|
set_color_management_enabled, set_corner_radius, set_default_workspace_capture,
|
||||||
set_explicit_sync_enabled, set_float_above_fullscreen, set_floating_titles, set_idle,
|
set_explicit_sync_enabled, set_float_above_fullscreen, set_floating_titles, set_idle,
|
||||||
set_idle_grace_period, set_middle_click_paste_enabled, set_show_bar,
|
set_idle_grace_period, set_middle_click_paste_enabled, set_show_bar,
|
||||||
set_show_float_pin_icon, set_show_titles, set_tab_title_align, set_ui_drag_enabled,
|
set_show_float_pin_icon, set_show_titles, set_tab_from_top, set_tab_render_text,
|
||||||
set_ui_drag_threshold,
|
set_tab_title_align, set_ui_drag_enabled, set_ui_drag_threshold,
|
||||||
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
||||||
switch_to_vt,
|
switch_to_vt,
|
||||||
tasks::{self, JoinHandle},
|
tasks::{self, JoinHandle},
|
||||||
|
|
@ -1023,10 +1023,16 @@ impl State {
|
||||||
color!(HIGHLIGHT_COLOR, highlight_color);
|
color!(HIGHLIGHT_COLOR, highlight_color);
|
||||||
color!(TAB_ACTIVE_BACKGROUND_COLOR, tab_active_bg_color);
|
color!(TAB_ACTIVE_BACKGROUND_COLOR, tab_active_bg_color);
|
||||||
color!(TAB_ACTIVE_BORDER_COLOR, tab_active_border_color);
|
color!(TAB_ACTIVE_BORDER_COLOR, tab_active_border_color);
|
||||||
|
color!(TAB_FOCUSED_BACKGROUND_COLOR, tab_focused_bg_color);
|
||||||
|
color!(TAB_FOCUSED_BORDER_COLOR, tab_focused_border_color);
|
||||||
color!(TAB_INACTIVE_BACKGROUND_COLOR, tab_inactive_bg_color);
|
color!(TAB_INACTIVE_BACKGROUND_COLOR, tab_inactive_bg_color);
|
||||||
color!(TAB_INACTIVE_BORDER_COLOR, tab_inactive_border_color);
|
color!(TAB_INACTIVE_BORDER_COLOR, tab_inactive_border_color);
|
||||||
|
color!(TAB_URGENT_BACKGROUND_COLOR, tab_urgent_bg_color);
|
||||||
|
color!(TAB_URGENT_BORDER_COLOR, tab_urgent_border_color);
|
||||||
color!(TAB_ACTIVE_TEXT_COLOR, tab_active_text_color);
|
color!(TAB_ACTIVE_TEXT_COLOR, tab_active_text_color);
|
||||||
|
color!(TAB_FOCUSED_TEXT_COLOR, tab_focused_text_color);
|
||||||
color!(TAB_INACTIVE_TEXT_COLOR, tab_inactive_text_color);
|
color!(TAB_INACTIVE_TEXT_COLOR, tab_inactive_text_color);
|
||||||
|
color!(TAB_URGENT_TEXT_COLOR, tab_urgent_text_color);
|
||||||
color!(TAB_BAR_BACKGROUND_COLOR, tab_bar_bg_color);
|
color!(TAB_BAR_BACKGROUND_COLOR, tab_bar_bg_color);
|
||||||
color!(TAB_ATTENTION_BACKGROUND_COLOR, tab_attention_bg_color);
|
color!(TAB_ATTENTION_BACKGROUND_COLOR, tab_attention_bg_color);
|
||||||
macro_rules! size {
|
macro_rules! size {
|
||||||
|
|
@ -1048,6 +1054,7 @@ impl State {
|
||||||
size!(TAB_BAR_BORDER_WIDTH, tab_bar_border_width);
|
size!(TAB_BAR_BORDER_WIDTH, tab_bar_border_width);
|
||||||
size!(TAB_BAR_TEXT_PADDING, tab_bar_text_padding);
|
size!(TAB_BAR_TEXT_PADDING, tab_bar_text_padding);
|
||||||
size!(TAB_BAR_GAP, tab_bar_gap);
|
size!(TAB_BAR_GAP, tab_bar_gap);
|
||||||
|
size!(TAB_OPACITY, tab_opacity);
|
||||||
macro_rules! font {
|
macro_rules! font {
|
||||||
($fun:ident, $field:ident) => {
|
($fun:ident, $field:ident) => {
|
||||||
if let Some(font) = &theme.$field {
|
if let Some(font) = &theme.$field {
|
||||||
|
|
@ -1064,6 +1071,12 @@ impl State {
|
||||||
if let Some(ref align) = theme.tab_title_align {
|
if let Some(ref align) = theme.tab_title_align {
|
||||||
set_tab_title_align(align);
|
set_tab_title_align(align);
|
||||||
}
|
}
|
||||||
|
if let Some(from_top) = theme.tab_from_top {
|
||||||
|
set_tab_from_top(from_top);
|
||||||
|
}
|
||||||
|
if let Some(render_text) = theme.tab_render_text {
|
||||||
|
set_tab_render_text(render_text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_switch_device(self: &Rc<Self>, dev: InputDevice, actions: &Rc<SwitchActions>) {
|
fn handle_switch_device(self: &Rc<Self>, dev: InputDevice, actions: &Rc<SwitchActions>) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue