Compare commits
2 commits
e1928863d9
...
cea4187fc0
| Author | SHA1 | Date | |
|---|---|---|---|
| cea4187fc0 | |||
| a41dbae899 |
57 changed files with 3056 additions and 1048 deletions
|
|
@ -20,10 +20,18 @@ pub const TREES: &[Tree] = &[
|
|||
"tex.frag",
|
||||
"out.vert",
|
||||
"out.frag",
|
||||
"rounded_fill.frag",
|
||||
"rounded_fill.vert",
|
||||
"rounded_tex.frag",
|
||||
"rounded_tex.vert",
|
||||
"legacy/fill.frag",
|
||||
"legacy/fill.vert",
|
||||
"legacy/tex.vert",
|
||||
"legacy/tex.frag",
|
||||
"legacy/rounded_fill.frag",
|
||||
"legacy/rounded_fill.vert",
|
||||
"legacy/rounded_tex.frag",
|
||||
"legacy/rounded_tex.vert",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2025,6 +2025,52 @@ impl ConfigClient {
|
|||
self.send(&ClientMessage::SetPointerRevertKey { seat, key });
|
||||
}
|
||||
|
||||
pub fn set_corner_radius(&self, radius: f32) {
|
||||
self.send(&ClientMessage::SetCornerRadius { radius });
|
||||
}
|
||||
|
||||
pub fn get_corner_radius(&self) -> f32 {
|
||||
let res = self.send_with_response(&ClientMessage::GetCornerRadius);
|
||||
get_response!(res, 0.0, GetCornerRadius { radius });
|
||||
radius
|
||||
}
|
||||
|
||||
pub fn seat_toggle_expand(&self, seat: Seat) {
|
||||
self.send(&ClientMessage::SeatToggleExpand { seat });
|
||||
}
|
||||
|
||||
pub fn seat_toggle_tab(&self, seat: Seat) {
|
||||
self.send(&ClientMessage::SeatToggleTab { seat });
|
||||
}
|
||||
|
||||
pub fn seat_make_group(&self, seat: Seat, axis: Axis, ephemeral: bool) {
|
||||
self.send(&ClientMessage::SeatMakeGroup {
|
||||
seat,
|
||||
axis,
|
||||
ephemeral,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn seat_change_group_opposite(&self, seat: Seat) {
|
||||
self.send(&ClientMessage::SeatChangeGroupOpposite { seat });
|
||||
}
|
||||
|
||||
pub fn seat_equalize(&self, seat: Seat, recursive: bool) {
|
||||
self.send(&ClientMessage::SeatEqualize { seat, recursive });
|
||||
}
|
||||
|
||||
pub fn set_autotile(&self, enabled: bool) {
|
||||
self.send(&ClientMessage::SetAutotile { enabled });
|
||||
}
|
||||
|
||||
pub fn set_tab_title_align(&self, align: u32) {
|
||||
self.send(&ClientMessage::SetTabTitleAlign { align });
|
||||
}
|
||||
|
||||
pub fn seat_move_tab(&self, seat: Seat, right: bool) {
|
||||
self.send(&ClientMessage::SeatMoveTab { seat, right });
|
||||
}
|
||||
|
||||
fn handle_msg(&self, msg: &[u8]) {
|
||||
self.handle_msg2(msg);
|
||||
self.dispatch_futures();
|
||||
|
|
|
|||
|
|
@ -880,6 +880,38 @@ pub enum ClientMessage<'a> {
|
|||
seat: Seat,
|
||||
enabled: bool,
|
||||
},
|
||||
SetCornerRadius {
|
||||
radius: f32,
|
||||
},
|
||||
GetCornerRadius,
|
||||
SeatToggleExpand {
|
||||
seat: Seat,
|
||||
},
|
||||
SeatToggleTab {
|
||||
seat: Seat,
|
||||
},
|
||||
SeatMakeGroup {
|
||||
seat: Seat,
|
||||
axis: Axis,
|
||||
ephemeral: bool,
|
||||
},
|
||||
SeatChangeGroupOpposite {
|
||||
seat: Seat,
|
||||
},
|
||||
SeatEqualize {
|
||||
seat: Seat,
|
||||
recursive: bool,
|
||||
},
|
||||
SetAutotile {
|
||||
enabled: bool,
|
||||
},
|
||||
SetTabTitleAlign {
|
||||
align: u32,
|
||||
},
|
||||
SeatMoveTab {
|
||||
seat: Seat,
|
||||
right: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
@ -1136,6 +1168,9 @@ pub enum Response {
|
|||
ConnectorSupportsArbitraryModes {
|
||||
supports_arbitrary_modes: bool,
|
||||
},
|
||||
GetCornerRadius {
|
||||
radius: f32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -677,6 +677,33 @@ impl Seat {
|
|||
pub fn unstable_set_mouse_follows_focus(self, enabled: bool) {
|
||||
get!().seat_set_mouse_follows_focus(self, enabled)
|
||||
}
|
||||
|
||||
/// Toggles tabbed mode on the focused window's parent container.
|
||||
pub fn toggle_tab(self) {
|
||||
get!().seat_toggle_tab(self)
|
||||
}
|
||||
|
||||
/// Wraps the focused child in a new sub-container with the given split axis.
|
||||
pub fn make_group(self, axis: Axis, ephemeral: bool) {
|
||||
get!().seat_make_group(self, axis, ephemeral)
|
||||
}
|
||||
|
||||
/// Toggles the parent container's split direction (H↔V).
|
||||
pub fn change_group_opposite(self) {
|
||||
get!().seat_change_group_opposite(self)
|
||||
}
|
||||
|
||||
/// Resets all siblings' size factors to equal.
|
||||
pub fn equalize(self, recursive: bool) {
|
||||
get!().seat_equalize(self, recursive)
|
||||
}
|
||||
|
||||
/// Move the active tab left or right within the current tab bar.
|
||||
///
|
||||
/// Equivalent to hy3's `movewindow` within a tabbed group.
|
||||
pub fn move_tab(self, right: bool) {
|
||||
get!().seat_move_tab(self, right)
|
||||
}
|
||||
}
|
||||
|
||||
/// A focus-follows-mouse mode.
|
||||
|
|
|
|||
|
|
@ -382,6 +382,42 @@ pub fn toggle_floating_titles() {
|
|||
get.set_floating_titles(!get.get_floating_titles());
|
||||
}
|
||||
|
||||
/// Sets the corner radius for window borders.
|
||||
///
|
||||
/// A radius of 0 means square corners. The radius is in logical pixels.
|
||||
pub fn set_corner_radius(radius: f32) {
|
||||
get!().set_corner_radius(radius)
|
||||
}
|
||||
|
||||
/// Returns the current corner radius for window borders.
|
||||
pub fn get_corner_radius() -> f32 {
|
||||
get!(0.0).get_corner_radius()
|
||||
}
|
||||
|
||||
/// Enables or disables autotiling.
|
||||
///
|
||||
/// When enabled, new windows are automatically placed in a perpendicular
|
||||
/// sub-container if the predicted body would be narrower than tall (or vice versa).
|
||||
///
|
||||
/// The default is `false`.
|
||||
pub fn set_autotile(enabled: bool) {
|
||||
get!().set_autotile(enabled)
|
||||
}
|
||||
|
||||
/// Sets the horizontal alignment of title text within tab buttons.
|
||||
///
|
||||
/// - `"start"` — left-aligned (default)
|
||||
/// - `"center"` — centered
|
||||
/// - `"end"` — right-aligned
|
||||
pub fn set_tab_title_align(align: &str) {
|
||||
let val = match align {
|
||||
"center" => 1,
|
||||
"end" => 2,
|
||||
_ => 0, // start
|
||||
};
|
||||
get!().set_tab_title_align(val)
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
|
|
|||
|
|
@ -302,6 +302,38 @@ pub mod colors {
|
|||
///
|
||||
/// Default: `#9d28c67f`.
|
||||
const 15 => HIGHLIGHT_COLOR,
|
||||
/// The background color of an active (focused) tab.
|
||||
///
|
||||
/// Default: `#33ccff40`.
|
||||
const 16 => TAB_ACTIVE_BACKGROUND_COLOR,
|
||||
/// The border color of an active (focused) tab.
|
||||
///
|
||||
/// Default: `#33ccffee`.
|
||||
const 17 => TAB_ACTIVE_BORDER_COLOR,
|
||||
/// The background color of an inactive tab.
|
||||
///
|
||||
/// Default: `#222222`.
|
||||
const 18 => TAB_INACTIVE_BACKGROUND_COLOR,
|
||||
/// The border color of an inactive tab.
|
||||
///
|
||||
/// Default: `#333333`.
|
||||
const 19 => TAB_INACTIVE_BORDER_COLOR,
|
||||
/// The text color of an active (focused) tab.
|
||||
///
|
||||
/// Default: `#ffffff`.
|
||||
const 20 => TAB_ACTIVE_TEXT_COLOR,
|
||||
/// The text color of an inactive tab.
|
||||
///
|
||||
/// Default: `#888888`.
|
||||
const 21 => TAB_INACTIVE_TEXT_COLOR,
|
||||
/// The background color of the tab bar strip.
|
||||
///
|
||||
/// Default: `#111111`.
|
||||
const 22 => TAB_BAR_BACKGROUND_COLOR,
|
||||
/// The background color of a tab that has requested attention.
|
||||
///
|
||||
/// Default: `#23092c`.
|
||||
const 23 => TAB_ATTENTION_BACKGROUND_COLOR,
|
||||
}
|
||||
|
||||
/// Sets the color of GUI element.
|
||||
|
|
@ -374,5 +406,29 @@ pub mod sized {
|
|||
///
|
||||
/// Default: 0
|
||||
const 06 => TITLE_GAP,
|
||||
/// The height of the tab bar in pixels.
|
||||
///
|
||||
/// Default: 22
|
||||
const 07 => TAB_BAR_HEIGHT,
|
||||
/// The padding between tabs in the tab bar in pixels.
|
||||
///
|
||||
/// Default: 6
|
||||
const 08 => TAB_BAR_PADDING,
|
||||
/// The corner radius of tabs in the tab bar in pixels.
|
||||
///
|
||||
/// Default: 6
|
||||
const 09 => TAB_BAR_RADIUS,
|
||||
/// The border width of tabs in the tab bar in pixels.
|
||||
///
|
||||
/// Default: 2
|
||||
const 10 => TAB_BAR_BORDER_WIDTH,
|
||||
/// The horizontal padding within each tab for text in pixels.
|
||||
///
|
||||
/// Default: 4
|
||||
const 11 => TAB_BAR_TEXT_PADDING,
|
||||
/// The gap between the tab bar and the window content below in pixels.
|
||||
///
|
||||
/// Default: 4
|
||||
const 12 => TAB_BAR_GAP,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,8 +60,9 @@ use {
|
|||
tree::{
|
||||
DisplayNode, NodeIds, OutputNode, TearingMode, Transform, VrrMode,
|
||||
WorkspaceDisplayOrder, WorkspaceNode, container_layout, container_render_positions,
|
||||
container_render_titles, float_layout, float_titles, output_render_data,
|
||||
float_layout, output_render_data,
|
||||
placeholder_render_textures,
|
||||
container_tab_render_textures,
|
||||
},
|
||||
user_session::import_environment,
|
||||
utils::{
|
||||
|
|
@ -259,14 +260,13 @@ fn start_compositor2(
|
|||
theme: Default::default(),
|
||||
pending_container_layout: Default::default(),
|
||||
pending_container_render_positions: Default::default(),
|
||||
pending_container_render_title: Default::default(),
|
||||
pending_output_render_data: Default::default(),
|
||||
pending_float_layout: Default::default(),
|
||||
pending_float_titles: Default::default(),
|
||||
pending_input_popup_positioning: Default::default(),
|
||||
pending_toplevel_screencasts: Default::default(),
|
||||
pending_screencast_reallocs_or_reconfigures: Default::default(),
|
||||
pending_placeholder_render_textures: Default::default(),
|
||||
pending_container_tab_render_textures: Default::default(),
|
||||
dbus: Dbus::new(&engine, &ring, &run_toplevel),
|
||||
fdcloser: FdCloser::new(),
|
||||
logger: logger.clone(),
|
||||
|
|
@ -511,16 +511,16 @@ fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {
|
|||
Phase::PostLayout,
|
||||
container_render_positions(state.clone()),
|
||||
),
|
||||
eng.spawn2(
|
||||
"container titles",
|
||||
Phase::PostLayout,
|
||||
container_render_titles(state.clone()),
|
||||
),
|
||||
eng.spawn2(
|
||||
"placeholder textures",
|
||||
Phase::PostLayout,
|
||||
placeholder_render_textures(state.clone()),
|
||||
),
|
||||
eng.spawn2(
|
||||
"container tab textures",
|
||||
Phase::PostLayout,
|
||||
container_tab_render_textures(state.clone()),
|
||||
),
|
||||
eng.spawn2(
|
||||
"output render",
|
||||
Phase::PostLayout,
|
||||
|
|
@ -531,11 +531,6 @@ fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {
|
|||
wlr_output_manager_done(state.clone()),
|
||||
),
|
||||
eng.spawn2("float layout", Phase::Layout, float_layout(state.clone())),
|
||||
eng.spawn2(
|
||||
"float titles",
|
||||
Phase::PostLayout,
|
||||
float_titles(state.clone()),
|
||||
),
|
||||
eng.spawn2("idle", Phase::PostLayout, idle(state.clone())),
|
||||
eng.spawn2(
|
||||
"input, popup positioning",
|
||||
|
|
|
|||
|
|
@ -1411,24 +1411,20 @@ impl ConfigProxyHandler {
|
|||
});
|
||||
}
|
||||
|
||||
fn handle_set_show_titles(&self, show: bool) {
|
||||
self.state.set_show_titles(show);
|
||||
fn handle_set_show_titles(&self, _show: bool) {
|
||||
// no-op: titles have been removed
|
||||
}
|
||||
|
||||
fn handle_get_show_titles(&self) {
|
||||
self.respond(Response::GetShowTitles {
|
||||
show: self.state.theme.show_titles.get(),
|
||||
});
|
||||
self.respond(Response::GetShowTitles { show: false });
|
||||
}
|
||||
|
||||
fn handle_set_floating_titles(&self, floating: bool) {
|
||||
self.state.set_floating_titles(floating);
|
||||
fn handle_set_floating_titles(&self, _floating: bool) {
|
||||
// no-op: titles have been removed
|
||||
}
|
||||
|
||||
fn handle_get_floating_titles(&self) {
|
||||
self.respond(Response::GetFloatingTitles {
|
||||
floating: self.state.theme.floating_titles.get(),
|
||||
});
|
||||
self.respond(Response::GetFloatingTitles { floating: false });
|
||||
}
|
||||
|
||||
fn handle_set_bar_position(&self, position: BarPosition) -> Result<(), CphError> {
|
||||
|
|
@ -1445,8 +1441,24 @@ impl ConfigProxyHandler {
|
|||
});
|
||||
}
|
||||
|
||||
fn handle_set_show_float_pin_icon(&self, show: bool) {
|
||||
self.state.set_show_pin_icon(show);
|
||||
fn handle_set_corner_radius(&self, radius: f32) {
|
||||
use crate::theme::CornerRadius;
|
||||
let radius = radius.max(0.0).min(1000.0);
|
||||
self.state
|
||||
.theme
|
||||
.corner_radius
|
||||
.set(CornerRadius::from(radius));
|
||||
self.state.damage(self.state.root.extents.get());
|
||||
}
|
||||
|
||||
fn handle_get_corner_radius(&self) {
|
||||
self.respond(Response::GetCornerRadius {
|
||||
radius: self.state.theme.corner_radius.get().top_left,
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_set_show_float_pin_icon(&self, _show: bool) {
|
||||
// no-op: titles have been removed, pin icon was in title bar
|
||||
}
|
||||
|
||||
fn handle_set_workspace_display_order(&self, order: WorkspaceDisplayOrder) {
|
||||
|
|
@ -1745,6 +1757,41 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_seat_toggle_tab(&self, seat: Seat) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
seat.toggle_tab();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_seat_make_group(
|
||||
&self,
|
||||
seat: Seat,
|
||||
axis: Axis,
|
||||
ephemeral: bool,
|
||||
) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
seat.make_group(axis.into(), ephemeral);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_seat_change_group_opposite(&self, seat: Seat) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
seat.change_group_opposite();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_seat_equalize(&self, seat: Seat, recursive: bool) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
seat.equalize(recursive);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_seat_move_tab(&self, seat: Seat, right: bool) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
seat.move_tab(right);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_get_window_split(&self, window: Window) -> Result<(), CphError> {
|
||||
let window = self.get_window(window)?;
|
||||
self.respond(Response::GetWindowSplit {
|
||||
|
|
@ -2452,6 +2499,12 @@ impl ConfigProxyHandler {
|
|||
BAR_SEPARATOR_WIDTH => ThemeSized::bar_separator_width,
|
||||
GAP => ThemeSized::gap,
|
||||
TITLE_GAP => ThemeSized::title_gap,
|
||||
TAB_BAR_HEIGHT => ThemeSized::tab_bar_height,
|
||||
TAB_BAR_PADDING => ThemeSized::tab_bar_padding,
|
||||
TAB_BAR_RADIUS => ThemeSized::tab_bar_radius,
|
||||
TAB_BAR_BORDER_WIDTH => ThemeSized::tab_bar_border_width,
|
||||
TAB_BAR_TEXT_PADDING => ThemeSized::tab_bar_text_padding,
|
||||
TAB_BAR_GAP => ThemeSized::tab_bar_gap,
|
||||
_ => return Err(CphError::UnknownSized(sized.0)),
|
||||
};
|
||||
Ok(sized)
|
||||
|
|
@ -2496,8 +2549,8 @@ impl ConfigProxyHandler {
|
|||
self.state.set_bar_font(Some(font));
|
||||
}
|
||||
|
||||
fn handle_set_title_font(&self, font: &str) {
|
||||
self.state.set_title_font(Some(font));
|
||||
fn handle_set_title_font(&self, _font: &str) {
|
||||
// no-op: titles have been removed
|
||||
}
|
||||
|
||||
fn handle_get_font(&self) {
|
||||
|
|
@ -2529,6 +2582,14 @@ impl ConfigProxyHandler {
|
|||
BAR_STATUS_TEXT_COLOR => ThemeColor::bar_text,
|
||||
ATTENTION_REQUESTED_BACKGROUND_COLOR => ThemeColor::attention_requested_background,
|
||||
HIGHLIGHT_COLOR => ThemeColor::highlight,
|
||||
TAB_ACTIVE_BACKGROUND_COLOR => ThemeColor::tab_active_background,
|
||||
TAB_ACTIVE_BORDER_COLOR => ThemeColor::tab_active_border,
|
||||
TAB_INACTIVE_BACKGROUND_COLOR => ThemeColor::tab_inactive_background,
|
||||
TAB_INACTIVE_BORDER_COLOR => ThemeColor::tab_inactive_border,
|
||||
TAB_ACTIVE_TEXT_COLOR => ThemeColor::tab_active_text,
|
||||
TAB_INACTIVE_TEXT_COLOR => ThemeColor::tab_inactive_text,
|
||||
TAB_BAR_BACKGROUND_COLOR => ThemeColor::tab_bar_background,
|
||||
TAB_ATTENTION_BACKGROUND_COLOR => ThemeColor::tab_attention_background,
|
||||
_ => return Err(CphError::UnknownColor(colorable.0)),
|
||||
};
|
||||
Ok(colorable)
|
||||
|
|
@ -3310,6 +3371,10 @@ impl ConfigProxyHandler {
|
|||
.handle_set_bar_position(position)
|
||||
.wrn("set_bar_position")?,
|
||||
ClientMessage::GetBarPosition => self.handle_get_bar_position(),
|
||||
ClientMessage::SetCornerRadius { radius } => {
|
||||
self.handle_set_corner_radius(radius)
|
||||
}
|
||||
ClientMessage::GetCornerRadius => self.handle_get_corner_radius(),
|
||||
ClientMessage::SeatFocusHistory { seat, timeline } => self
|
||||
.handle_seat_focus_history(seat, timeline)
|
||||
.wrn("seat_focus_history")?,
|
||||
|
|
@ -3432,6 +3497,40 @@ impl ConfigProxyHandler {
|
|||
} => self
|
||||
.handle_window_resize(window, dx1, dy1, dx2, dy2)
|
||||
.wrn("window_resize")?,
|
||||
ClientMessage::SeatToggleTab { seat } => self
|
||||
.handle_seat_toggle_tab(seat)
|
||||
.wrn("seat_toggle_tab")?,
|
||||
ClientMessage::SeatMakeGroup {
|
||||
seat,
|
||||
axis,
|
||||
ephemeral,
|
||||
} => self
|
||||
.handle_seat_make_group(seat, axis, ephemeral)
|
||||
.wrn("seat_make_group")?,
|
||||
ClientMessage::SeatChangeGroupOpposite { seat } => self
|
||||
.handle_seat_change_group_opposite(seat)
|
||||
.wrn("seat_change_group_opposite")?,
|
||||
ClientMessage::SeatEqualize { seat, recursive } => self
|
||||
.handle_seat_equalize(seat, recursive)
|
||||
.wrn("seat_equalize")?,
|
||||
ClientMessage::SetAutotile { enabled } => {
|
||||
self.state.theme.autotile_enabled.set(enabled);
|
||||
}
|
||||
ClientMessage::SeatToggleExpand { .. } => {
|
||||
// Removed feature; kept for binary protocol compatibility.
|
||||
}
|
||||
ClientMessage::SetTabTitleAlign { align } => {
|
||||
use crate::theme::TabTitleAlign;
|
||||
let val = match align {
|
||||
1 => TabTitleAlign::Center,
|
||||
2 => TabTitleAlign::End,
|
||||
_ => TabTitleAlign::Start,
|
||||
};
|
||||
self.state.theme.tab_title_align.set(val);
|
||||
}
|
||||
ClientMessage::SeatMoveTab { seat, right } => self
|
||||
.handle_seat_move_tab(seat, right)
|
||||
.wrn("seat_move_tab")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,8 @@ pub enum GfxApiOpt {
|
|||
Sync,
|
||||
FillRect(FillRect),
|
||||
CopyTexture(CopyTexture),
|
||||
RoundedFillRect(RoundedFillRect),
|
||||
RoundedCopyTexture(RoundedCopyTexture),
|
||||
}
|
||||
|
||||
pub struct GfxRenderPass {
|
||||
|
|
@ -289,6 +291,55 @@ pub struct CopyTexture {
|
|||
pub alpha_mode: AlphaMode,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RoundedFillRect {
|
||||
pub rect: FramebufferRect,
|
||||
pub color: Color,
|
||||
pub alpha: Option<f32>,
|
||||
pub render_intent: RenderIntent,
|
||||
pub cd: Rc<LinearColorDescription>,
|
||||
/// Size of the rectangle in physical pixels.
|
||||
pub size: [f32; 2],
|
||||
/// Per-corner radius in physical pixels: [top_left, top_right, bottom_right, bottom_left].
|
||||
pub corner_radius: [f32; 4],
|
||||
/// Border width in physical pixels. 0 means a filled rounded rect (no cutout).
|
||||
pub border_width: f32,
|
||||
/// Output scale for antialiasing.
|
||||
pub scale: f32,
|
||||
/// Sort order hint within the RoundedFill bucket (lower renders first).
|
||||
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 tex: Rc<dyn GfxTexture>,
|
||||
pub source: SampleRect,
|
||||
pub target: FramebufferRect,
|
||||
pub buffer_resv: Option<Rc<dyn BufferResv>>,
|
||||
pub acquire_sync: AcquireSync,
|
||||
pub release_sync: ReleaseSync,
|
||||
pub alpha: Option<f32>,
|
||||
pub opaque: bool,
|
||||
pub render_intent: RenderIntent,
|
||||
pub cd: Rc<ColorDescription>,
|
||||
pub alpha_mode: AlphaMode,
|
||||
/// Size of the rectangle in physical pixels.
|
||||
pub size: [f32; 2],
|
||||
/// Per-corner radius in physical pixels: [top_left, top_right, bottom_right, bottom_left].
|
||||
pub corner_radius: [f32; 4],
|
||||
/// Output scale for antialiasing.
|
||||
pub scale: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SyncFile(pub Rc<OwnedFd>);
|
||||
|
||||
|
|
@ -705,8 +756,8 @@ impl dyn GfxFramebuffer {
|
|||
let (width, height) = self.logical_size(transform);
|
||||
Rect::new_saturating(0, 0, width, height)
|
||||
},
|
||||
icons: None,
|
||||
stretch: None,
|
||||
corner_radius: None,
|
||||
};
|
||||
cursor.render_hardware_cursor(&mut renderer);
|
||||
self.render(
|
||||
|
|
@ -1039,8 +1090,8 @@ pub fn create_render_pass(
|
|||
let (width, height) = logical_size(physical_size, transform);
|
||||
Rect::new_saturating(0, 0, width, height)
|
||||
},
|
||||
icons: state.icons.get(state, scale),
|
||||
stretch: None,
|
||||
corner_radius: None,
|
||||
};
|
||||
node.node_render(&mut renderer, 0, 0, None);
|
||||
if let Some(rect) = cursor_rect {
|
||||
|
|
@ -1256,6 +1307,8 @@ impl GfxRenderPass {
|
|||
return None;
|
||||
}
|
||||
GfxApiOpt::CopyTexture(ct) => break 'ct2 ct,
|
||||
GfxApiOpt::RoundedFillRect(_) => return None,
|
||||
GfxApiOpt::RoundedCopyTexture(_) => return None,
|
||||
}
|
||||
}
|
||||
return None;
|
||||
|
|
@ -1299,6 +1352,8 @@ impl GfxRenderPass {
|
|||
// Texture could be visible.
|
||||
return None;
|
||||
}
|
||||
GfxApiOpt::RoundedFillRect(_) => return None,
|
||||
GfxApiOpt::RoundedCopyTexture(_) => return None,
|
||||
}
|
||||
}
|
||||
if let Some(clear) = self.clear
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use {
|
|||
cmm::cmm_eotf::Eotf,
|
||||
gfx_api::{
|
||||
AcquireSync, CopyTexture, FdSync, FramebufferRect, GfxApiOpt, GfxContext, GfxError,
|
||||
GfxTexture, ReleaseSync, SyncFile,
|
||||
GfxTexture, ReleaseSync, RoundedCopyTexture, RoundedFillRect, SyncFile,
|
||||
},
|
||||
gfx_apis::gl::{
|
||||
egl::image::EglImage,
|
||||
|
|
@ -217,6 +217,14 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option<FdSync> {
|
|||
copy_tex.push(c);
|
||||
i += 1;
|
||||
}
|
||||
GfxApiOpt::RoundedFillRect(rf) => {
|
||||
render_rounded_fill(&fb.ctx, rf);
|
||||
i += 1;
|
||||
}
|
||||
GfxApiOpt::RoundedCopyTexture(ct) => {
|
||||
render_rounded_texture(&fb.ctx, ct);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if fill_rect.is_not_empty() {
|
||||
|
|
@ -269,6 +277,12 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option<FdSync> {
|
|||
{
|
||||
resv.set_sync(user, &file);
|
||||
}
|
||||
if let GfxApiOpt::RoundedCopyTexture(ct) = op
|
||||
&& ct.release_sync == ReleaseSync::Explicit
|
||||
&& let Some(resv) = &ct.buffer_resv
|
||||
{
|
||||
resv.set_sync(user, &file);
|
||||
}
|
||||
}
|
||||
return Some(file);
|
||||
}
|
||||
|
|
@ -376,6 +390,114 @@ fn render_texture(ctx: &GlRenderContext, tex: &CopyTexture) {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_rounded_fill(ctx: &GlRenderContext, rf: &RoundedFillRect) {
|
||||
let gles = ctx.ctx.dpy.gles;
|
||||
let prog = &ctx.rounded_fill_prog;
|
||||
let pos = rf.rect.to_points();
|
||||
// geo_pos maps vertex corners to pixel coordinates within the rectangle
|
||||
let [w, h] = rf.size;
|
||||
let geo: [[f32; 2]; 4] = [[w, 0.0], [0.0, 0.0], [w, h], [0.0, h]];
|
||||
let [r, g, b, a] = rf.color.to_array(Eotf::Gamma22);
|
||||
let a = a * rf.alpha.unwrap_or(1.0);
|
||||
unsafe {
|
||||
(gles.glEnable)(GL_BLEND);
|
||||
(gles.glUseProgram)(prog.prog.prog);
|
||||
(gles.glUniform4f)(prog.color, r * a, g * a, b * a, a);
|
||||
(gles.glUniform2f)(prog.size, rf.size[0], rf.size[1]);
|
||||
(gles.glUniform4f)(
|
||||
prog.corner_radius,
|
||||
rf.corner_radius[0],
|
||||
rf.corner_radius[1],
|
||||
rf.corner_radius[2],
|
||||
rf.corner_radius[3],
|
||||
);
|
||||
(gles.glUniform1f)(prog.border_width, rf.border_width);
|
||||
(gles.glUniform1f)(prog.scale, rf.scale);
|
||||
(gles.glVertexAttribPointer)(prog.pos as _, 2, GL_FLOAT, GL_FALSE, 0, pos.as_ptr() as _);
|
||||
(gles.glVertexAttribPointer)(prog.geo as _, 2, GL_FLOAT, GL_FALSE, 0, geo.as_ptr() as _);
|
||||
(gles.glEnableVertexAttribArray)(prog.pos as _);
|
||||
(gles.glEnableVertexAttribArray)(prog.geo as _);
|
||||
(gles.glDrawArrays)(GL_TRIANGLE_STRIP, 0, 4);
|
||||
(gles.glDisableVertexAttribArray)(prog.pos as _);
|
||||
(gles.glDisableVertexAttribArray)(prog.geo as _);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_rounded_texture(ctx: &GlRenderContext, ct: &RoundedCopyTexture) {
|
||||
let Some(texture) = ct.tex.as_gl() else {
|
||||
log::error!("A non-OpenGL texture was passed into OpenGL");
|
||||
return;
|
||||
};
|
||||
if !texture.gl.contents_valid.get() {
|
||||
log::error!("Ignoring texture with invalid contents");
|
||||
return;
|
||||
}
|
||||
assert!(rc_eq(&ctx.ctx, &texture.ctx.ctx));
|
||||
let gles = ctx.ctx.dpy.gles;
|
||||
unsafe {
|
||||
handle_explicit_sync(ctx, texture.gl.img.as_ref(), &ct.acquire_sync);
|
||||
(gles.glActiveTexture)(GL_TEXTURE0);
|
||||
let target = image_target(texture.gl.external_only);
|
||||
(gles.glBindTexture)(target, texture.gl.tex);
|
||||
(gles.glTexParameteri)(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
let progs = match texture.gl.external_only {
|
||||
true => match &ctx.rounded_tex_external {
|
||||
Some(p) => p,
|
||||
_ => {
|
||||
log::error!(
|
||||
"Trying to render an external-only texture but context does not support the required extension"
|
||||
);
|
||||
return;
|
||||
}
|
||||
},
|
||||
false => &ctx.rounded_tex_internal,
|
||||
};
|
||||
let copy_type = match ct.alpha.is_some() {
|
||||
true => TexCopyType::Multiply,
|
||||
false => TexCopyType::Identity,
|
||||
};
|
||||
let source_type = TexSourceType::HasAlpha;
|
||||
(gles.glEnable)(GL_BLEND);
|
||||
let prog = &progs[copy_type][source_type];
|
||||
(gles.glUseProgram)(prog.prog.prog);
|
||||
(gles.glUniform1i)(prog.tex, 0);
|
||||
(gles.glUniform2f)(prog.size, ct.size[0], ct.size[1]);
|
||||
(gles.glUniform4f)(
|
||||
prog.corner_radius,
|
||||
ct.corner_radius[0],
|
||||
ct.corner_radius[1],
|
||||
ct.corner_radius[2],
|
||||
ct.corner_radius[3],
|
||||
);
|
||||
(gles.glUniform1f)(prog.scale, ct.scale);
|
||||
if let Some(alpha) = ct.alpha {
|
||||
(gles.glUniform1f)(prog.alpha, alpha);
|
||||
}
|
||||
let texcoord = ct.source.to_points();
|
||||
let pos = ct.target.to_points();
|
||||
let [w, h] = ct.size;
|
||||
let geo: [[f32; 2]; 4] = [[w, 0.0], [0.0, 0.0], [w, h], [0.0, h]];
|
||||
(gles.glVertexAttribPointer)(
|
||||
prog.texcoord as _,
|
||||
2,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
0,
|
||||
texcoord.as_ptr() as _,
|
||||
);
|
||||
(gles.glVertexAttribPointer)(prog.pos as _, 2, GL_FLOAT, GL_FALSE, 0, pos.as_ptr() as _);
|
||||
(gles.glVertexAttribPointer)(prog.geo as _, 2, GL_FLOAT, GL_FALSE, 0, geo.as_ptr() as _);
|
||||
(gles.glEnableVertexAttribArray)(prog.texcoord as _);
|
||||
(gles.glEnableVertexAttribArray)(prog.pos as _);
|
||||
(gles.glEnableVertexAttribArray)(prog.geo as _);
|
||||
(gles.glDrawArrays)(GL_TRIANGLE_STRIP, 0, 4);
|
||||
(gles.glDisableVertexAttribArray)(prog.texcoord as _);
|
||||
(gles.glDisableVertexAttribArray)(prog.pos as _);
|
||||
(gles.glDisableVertexAttribArray)(prog.geo as _);
|
||||
(gles.glBindTexture)(target, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_explicit_sync(ctx: &GlRenderContext, img: Option<&Rc<EglImage>>, sync: &AcquireSync) {
|
||||
let Some(sync_file) = sync.get_sync_file() else {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ dynload! {
|
|||
glGetAttribLocation: unsafe fn(prog: GLuint, name: *const GLchar) -> GLint,
|
||||
glUniform1i: unsafe fn(location: GLint, v0: GLint),
|
||||
glUniform1f: unsafe fn(location: GLint, v0: GLfloat),
|
||||
glUniform2f: unsafe fn(location: GLint, v0: GLfloat, v1: GLfloat),
|
||||
glUniform4f: unsafe fn(location: GLint, v0: GLfloat, v1: GLfloat, v2: GLfloat, v3: GLfloat),
|
||||
glVertexAttribPointer: unsafe fn(
|
||||
index: GLuint,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,29 @@ impl TexProg {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RoundedFillProg {
|
||||
pub(crate) prog: GlProgram,
|
||||
pub(crate) pos: GLint,
|
||||
pub(crate) geo: GLint,
|
||||
pub(crate) color: GLint,
|
||||
pub(crate) size: GLint,
|
||||
pub(crate) corner_radius: GLint,
|
||||
pub(crate) border_width: GLint,
|
||||
pub(crate) scale: GLint,
|
||||
}
|
||||
|
||||
pub(crate) struct RoundedTexProg {
|
||||
pub(crate) prog: GlProgram,
|
||||
pub(crate) pos: GLint,
|
||||
pub(crate) texcoord: GLint,
|
||||
pub(crate) geo: GLint,
|
||||
pub(crate) tex: GLint,
|
||||
pub(crate) alpha: GLint,
|
||||
pub(crate) size: GLint,
|
||||
pub(crate) corner_radius: GLint,
|
||||
pub(crate) scale: GLint,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Linearize)]
|
||||
pub(in crate::gfx_apis::gl) enum TexCopyType {
|
||||
Identity,
|
||||
|
|
@ -86,6 +109,12 @@ pub(in crate::gfx_apis::gl) struct GlRenderContext {
|
|||
pub(crate) fill_prog_pos: GLint,
|
||||
pub(crate) fill_prog_color: GLint,
|
||||
|
||||
pub(crate) rounded_fill_prog: RoundedFillProg,
|
||||
pub(crate) rounded_tex_internal:
|
||||
StaticMap<TexCopyType, StaticMap<TexSourceType, RoundedTexProg>>,
|
||||
pub(crate) rounded_tex_external:
|
||||
Option<StaticMap<TexCopyType, StaticMap<TexSourceType, RoundedTexProg>>>,
|
||||
|
||||
pub(in crate::gfx_apis::gl) gl_state: RefCell<GfxGlState>,
|
||||
|
||||
pub(in crate::gfx_apis::gl) buffer_resv_user: BufferResvUser,
|
||||
|
|
@ -163,6 +192,74 @@ impl GlRenderContext {
|
|||
include_str!("../shaders/fill.frag.glsl"),
|
||||
)?
|
||||
};
|
||||
let rounded_fill_prog = unsafe {
|
||||
let prog = GlProgram::from_shaders(
|
||||
ctx,
|
||||
include_str!("../shaders/rounded_fill.vert.glsl"),
|
||||
include_str!("../shaders/rounded_fill.frag.glsl"),
|
||||
)?;
|
||||
RoundedFillProg {
|
||||
pos: prog.get_attrib_location(c"pos"),
|
||||
geo: prog.get_attrib_location(c"geo"),
|
||||
color: prog.get_uniform_location(c"color"),
|
||||
size: prog.get_uniform_location(c"size"),
|
||||
corner_radius: prog.get_uniform_location(c"corner_radius"),
|
||||
border_width: prog.get_uniform_location(c"border_width"),
|
||||
scale: prog.get_uniform_location(c"scale"),
|
||||
prog,
|
||||
}
|
||||
};
|
||||
let rounded_tex_vert = include_str!("../shaders/rounded_tex.vert.glsl");
|
||||
let rounded_tex_frag = include_str!("../shaders/rounded_tex.frag.glsl");
|
||||
let create_rounded_tex_programs = |external: bool| {
|
||||
let create_program = |alpha_multiplier: bool, alpha: bool| {
|
||||
let mut src = String::new();
|
||||
if external {
|
||||
src.push_str("#define EXTERNAL\n");
|
||||
}
|
||||
if alpha_multiplier {
|
||||
src.push_str("#define ALPHA_MULTIPLIER\n");
|
||||
}
|
||||
if alpha {
|
||||
src.push_str("#define ALPHA\n");
|
||||
}
|
||||
src.push_str(rounded_tex_frag);
|
||||
unsafe {
|
||||
let prog = GlProgram::from_shaders(ctx, rounded_tex_vert, &src)?;
|
||||
let alpha_loc = match alpha_multiplier {
|
||||
true => prog.get_uniform_location(c"alpha"),
|
||||
false => 0,
|
||||
};
|
||||
Ok::<_, RenderError>(RoundedTexProg {
|
||||
pos: prog.get_attrib_location(c"pos"),
|
||||
texcoord: prog.get_attrib_location(c"texcoord"),
|
||||
geo: prog.get_attrib_location(c"geo"),
|
||||
tex: prog.get_uniform_location(c"tex"),
|
||||
alpha: alpha_loc,
|
||||
size: prog.get_uniform_location(c"size"),
|
||||
corner_radius: prog.get_uniform_location(c"corner_radius"),
|
||||
scale: prog.get_uniform_location(c"scale"),
|
||||
prog,
|
||||
})
|
||||
}
|
||||
};
|
||||
Ok::<_, RenderError>(static_map! {
|
||||
TexCopyType::Identity => static_map! {
|
||||
TexSourceType::Opaque => create_program(false, false)?,
|
||||
TexSourceType::HasAlpha => create_program(false, true)?,
|
||||
},
|
||||
TexCopyType::Multiply => static_map! {
|
||||
TexSourceType::Opaque => create_program(true, false)?,
|
||||
TexSourceType::HasAlpha => create_program(true, true)?,
|
||||
},
|
||||
})
|
||||
};
|
||||
let rounded_tex_internal = create_rounded_tex_programs(false)?;
|
||||
let rounded_tex_external = if ctx.ext.contains(GL_OES_EGL_IMAGE_EXTERNAL) {
|
||||
Some(create_rounded_tex_programs(true)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Self {
|
||||
ctx: ctx.clone(),
|
||||
gbm: ctx.dpy.gbm.clone(),
|
||||
|
|
@ -177,6 +274,10 @@ impl GlRenderContext {
|
|||
fill_prog_color: unsafe { fill_prog.get_uniform_location(c"color") },
|
||||
fill_prog,
|
||||
|
||||
rounded_fill_prog,
|
||||
rounded_tex_internal,
|
||||
rounded_tex_external,
|
||||
|
||||
gl_state: Default::default(),
|
||||
|
||||
buffer_resv_user: Default::default(),
|
||||
|
|
|
|||
56
src/gfx_apis/gl/shaders/rounded_fill.frag.glsl
Normal file
56
src/gfx_apis/gl/shaders/rounded_fill.frag.glsl
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
precision mediump float;
|
||||
|
||||
varying vec2 v_geo;
|
||||
|
||||
uniform vec4 color;
|
||||
uniform vec2 size;
|
||||
uniform vec4 corner_radius;
|
||||
uniform float border_width;
|
||||
uniform float scale;
|
||||
|
||||
float rounding_alpha(vec2 coords, vec2 half_size, vec4 radii) {
|
||||
float radius;
|
||||
vec2 center;
|
||||
|
||||
if (coords.x < half_size.x && coords.y < half_size.y) {
|
||||
radius = radii.x; // top-left
|
||||
center = vec2(radius, radius);
|
||||
} else if (coords.x >= half_size.x && coords.y < half_size.y) {
|
||||
radius = radii.y; // top-right
|
||||
center = vec2(size.x - radius, radius);
|
||||
} else if (coords.x >= half_size.x && coords.y >= half_size.y) {
|
||||
radius = radii.z; // bottom-right
|
||||
center = vec2(size.x - radius, size.y - radius);
|
||||
} else {
|
||||
radius = radii.w; // bottom-left
|
||||
center = vec2(radius, size.y - radius);
|
||||
}
|
||||
|
||||
if (radius == 0.0)
|
||||
return 1.0;
|
||||
|
||||
float dist = distance(coords, center);
|
||||
float half_px = 0.5 / scale;
|
||||
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 half_size = size / 2.0;
|
||||
float outer_alpha = rounding_alpha(v_geo, half_size, corner_radius);
|
||||
|
||||
float inner_alpha = 0.0;
|
||||
if (border_width > 0.0) {
|
||||
vec2 inner_coords = v_geo - vec2(border_width);
|
||||
vec2 inner_size = size - vec2(border_width * 2.0);
|
||||
vec2 inner_half = inner_size / 2.0;
|
||||
vec4 inner_radii = max(corner_radius - vec4(border_width), vec4(0.0));
|
||||
|
||||
if (inner_coords.x >= 0.0 && inner_coords.y >= 0.0 &&
|
||||
inner_coords.x <= inner_size.x && inner_coords.y <= inner_size.y) {
|
||||
inner_alpha = rounding_alpha(inner_coords, inner_half, inner_radii);
|
||||
}
|
||||
}
|
||||
|
||||
float a = outer_alpha * (1.0 - inner_alpha);
|
||||
gl_FragColor = color * a;
|
||||
}
|
||||
8
src/gfx_apis/gl/shaders/rounded_fill.vert.glsl
Normal file
8
src/gfx_apis/gl/shaders/rounded_fill.vert.glsl
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
attribute vec2 pos;
|
||||
attribute vec2 geo;
|
||||
varying vec2 v_geo;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
v_geo = geo;
|
||||
}
|
||||
71
src/gfx_apis/gl/shaders/rounded_tex.frag.glsl
Normal file
71
src/gfx_apis/gl/shaders/rounded_tex.frag.glsl
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#ifdef EXTERNAL
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
#endif
|
||||
|
||||
precision mediump float;
|
||||
|
||||
varying vec2 v_texcoord;
|
||||
varying vec2 v_geo;
|
||||
|
||||
#ifdef EXTERNAL
|
||||
uniform samplerExternalOES tex;
|
||||
#else
|
||||
uniform sampler2D tex;
|
||||
#endif
|
||||
#ifdef ALPHA_MULTIPLIER
|
||||
uniform float alpha;
|
||||
#endif
|
||||
uniform vec2 size;
|
||||
uniform vec4 corner_radius;
|
||||
uniform float scale;
|
||||
|
||||
float rounding_alpha(vec2 coords, vec2 half_size, vec4 radii) {
|
||||
float radius;
|
||||
vec2 center;
|
||||
|
||||
if (coords.x < half_size.x && coords.y < half_size.y) {
|
||||
radius = radii.x; // top-left
|
||||
center = vec2(radius, radius);
|
||||
} else if (coords.x >= half_size.x && coords.y < half_size.y) {
|
||||
radius = radii.y; // top-right
|
||||
center = vec2(size.x - radius, radius);
|
||||
} else if (coords.x >= half_size.x && coords.y >= half_size.y) {
|
||||
radius = radii.z; // bottom-right
|
||||
center = vec2(size.x - radius, size.y - radius);
|
||||
} else {
|
||||
radius = radii.w; // bottom-left
|
||||
center = vec2(radius, size.y - radius);
|
||||
}
|
||||
|
||||
if (radius == 0.0)
|
||||
return 1.0;
|
||||
|
||||
float dist = distance(coords, center);
|
||||
float half_px = 0.5 / scale;
|
||||
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 half_size = size / 2.0;
|
||||
float ra = rounding_alpha(v_geo, half_size, corner_radius);
|
||||
|
||||
#ifdef ALPHA
|
||||
|
||||
#ifdef ALPHA_MULTIPLIER
|
||||
gl_FragColor = texture2D(tex, v_texcoord) * alpha * ra;
|
||||
#else // !ALPHA_MULTIPLIER
|
||||
gl_FragColor = texture2D(tex, v_texcoord) * ra;
|
||||
#endif // ALPHA_MULTIPLIER
|
||||
|
||||
#else // !ALPHA
|
||||
|
||||
#ifdef ALPHA_MULTIPLIER
|
||||
vec4 tc = texture2D(tex, v_texcoord);
|
||||
gl_FragColor = vec4(tc.rgb * alpha * ra, alpha * ra);
|
||||
#else // !ALPHA_MULTIPLIER
|
||||
vec4 tc = texture2D(tex, v_texcoord);
|
||||
gl_FragColor = vec4(tc.rgb * ra, ra);
|
||||
#endif // ALPHA_MULTIPLIER
|
||||
|
||||
#endif // ALPHA
|
||||
}
|
||||
11
src/gfx_apis/gl/shaders/rounded_tex.vert.glsl
Normal file
11
src/gfx_apis/gl/shaders/rounded_tex.vert.glsl
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
attribute vec2 pos;
|
||||
attribute vec2 texcoord;
|
||||
attribute vec2 geo;
|
||||
varying vec2 v_texcoord;
|
||||
varying vec2 v_geo;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
v_texcoord = texcoord;
|
||||
v_geo = geo;
|
||||
}
|
||||
|
|
@ -27,9 +27,14 @@ use {
|
|||
semaphore::VulkanSemaphore,
|
||||
shaders::{
|
||||
ColorManagementData, EotfArgs, FILL_FRAG, FILL_VERT, FillPushConstants,
|
||||
InvEotfArgs, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, LEGACY_TEX_FRAG, LEGACY_TEX_VERT,
|
||||
LegacyFillPushConstants, LegacyTexPushConstants, OUT_FRAG, OUT_VERT,
|
||||
OutPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants, TexVertex, VulkanShader,
|
||||
InvEotfArgs, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, LEGACY_ROUNDED_FILL_FRAG,
|
||||
LEGACY_ROUNDED_FILL_VERT, LEGACY_ROUNDED_TEX_FRAG, LEGACY_ROUNDED_TEX_VERT,
|
||||
LEGACY_TEX_FRAG, LEGACY_TEX_VERT, LegacyFillPushConstants,
|
||||
LegacyRoundedFillPushConstants, LegacyRoundedTexPushConstants,
|
||||
LegacyTexPushConstants, OUT_FRAG, OUT_VERT, OutPushConstants, ROUNDED_FILL_FRAG,
|
||||
ROUNDED_FILL_VERT, ROUNDED_TEX_FRAG, ROUNDED_TEX_VERT, RoundedFillPushConstants,
|
||||
RoundedTexPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants, TexVertex,
|
||||
VulkanShader,
|
||||
},
|
||||
},
|
||||
io_uring::IoUring,
|
||||
|
|
@ -104,6 +109,13 @@ pub struct VulkanRenderer {
|
|||
pub(super) tex_frag_shader: Rc<VulkanShader>,
|
||||
pub(super) out_vert_shader: Option<Rc<VulkanShader>>,
|
||||
pub(super) out_frag_shader: Option<Rc<VulkanShader>>,
|
||||
pub(super) rounded_fill_vert_shader: Rc<VulkanShader>,
|
||||
pub(super) rounded_fill_frag_shader: Rc<VulkanShader>,
|
||||
pub(super) rounded_tex_vert_shader: Rc<VulkanShader>,
|
||||
pub(super) rounded_tex_frag_shader: Rc<VulkanShader>,
|
||||
pub(super) rounded_fill_pipelines: CopyHashMap<vk::Format, FillPipelines>,
|
||||
pub(super) rounded_tex_pipelines:
|
||||
StaticMap<VulkanEotf, CopyHashMap<vk::Format, Rc<TexPipelines>>>,
|
||||
pub(super) tex_descriptor_set_layouts: ArrayVec<Rc<VulkanDescriptorSetLayout>, 2>,
|
||||
pub(super) out_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>,
|
||||
pub(super) defunct: Cell<bool>,
|
||||
|
|
@ -202,6 +214,8 @@ type Point = [[f32; 2]; 4];
|
|||
enum VulkanOp {
|
||||
Fill(VulkanFillOp),
|
||||
Tex(VulkanTexOp),
|
||||
RoundedFill(VulkanRoundedFillOp),
|
||||
RoundedTex(VulkanRoundedTexOp),
|
||||
}
|
||||
|
||||
struct VulkanTexOp {
|
||||
|
|
@ -231,6 +245,40 @@ struct VulkanFillOp {
|
|||
instances: u32,
|
||||
}
|
||||
|
||||
struct VulkanRoundedFillOp {
|
||||
target: Point,
|
||||
color: [f32; 4],
|
||||
source_type: TexSourceType,
|
||||
size: [f32; 2],
|
||||
corner_radius: [f32; 4],
|
||||
border_width: f32,
|
||||
scale: f32,
|
||||
range_address: DeviceAddress,
|
||||
z_order: u32,
|
||||
}
|
||||
|
||||
struct VulkanRoundedTexOp {
|
||||
tex: Rc<VulkanImage>,
|
||||
index: usize,
|
||||
target: Point,
|
||||
source: Point,
|
||||
buffer_resv: Option<Rc<dyn BufferResv>>,
|
||||
acquire_sync: Option<AcquireSync>,
|
||||
release_sync: ReleaseSync,
|
||||
alpha: f32,
|
||||
source_type: TexSourceType,
|
||||
copy_type: TexCopyType,
|
||||
alpha_mode: AlphaMode,
|
||||
tex_cd: Rc<ColorDescription>,
|
||||
color_management_data_address: Option<DeviceAddress>,
|
||||
eotf_args_address: Option<DeviceAddress>,
|
||||
resource_descriptor_buffer_offset: DeviceAddress,
|
||||
size: [f32; 2],
|
||||
corner_radius: [f32; 4],
|
||||
scale: f32,
|
||||
range_address: DeviceAddress,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Linearize, Eq, PartialEq)]
|
||||
pub(super) enum RenderPass {
|
||||
BlendBuffer,
|
||||
|
|
@ -295,12 +343,20 @@ impl VulkanDevice {
|
|||
let tex_frag_shader;
|
||||
let out_vert_shader;
|
||||
let out_frag_shader;
|
||||
let rounded_fill_vert_shader;
|
||||
let rounded_fill_frag_shader;
|
||||
let rounded_tex_vert_shader;
|
||||
let rounded_tex_frag_shader;
|
||||
let mut tex_descriptor_set_layouts = ArrayVec::new();
|
||||
if self.descriptor_buffer.is_some() {
|
||||
tex_vert_shader = self.create_shader(TEX_VERT)?;
|
||||
tex_frag_shader = self.create_shader(TEX_FRAG)?;
|
||||
fill_vert_shader = self.create_shader(FILL_VERT)?;
|
||||
fill_frag_shader = self.create_shader(FILL_FRAG)?;
|
||||
rounded_fill_vert_shader = self.create_shader(ROUNDED_FILL_VERT)?;
|
||||
rounded_fill_frag_shader = self.create_shader(ROUNDED_FILL_FRAG)?;
|
||||
rounded_tex_vert_shader = self.create_shader(ROUNDED_TEX_VERT)?;
|
||||
rounded_tex_frag_shader = self.create_shader(ROUNDED_TEX_FRAG)?;
|
||||
out_vert_shader = Some(self.create_shader(OUT_VERT)?);
|
||||
out_frag_shader = Some(self.create_shader(OUT_FRAG)?);
|
||||
tex_descriptor_set_layouts
|
||||
|
|
@ -311,6 +367,10 @@ impl VulkanDevice {
|
|||
tex_frag_shader = self.create_shader(LEGACY_TEX_FRAG)?;
|
||||
fill_vert_shader = self.create_shader(LEGACY_FILL_VERT)?;
|
||||
fill_frag_shader = self.create_shader(LEGACY_FILL_FRAG)?;
|
||||
rounded_fill_vert_shader = self.create_shader(LEGACY_ROUNDED_FILL_VERT)?;
|
||||
rounded_fill_frag_shader = self.create_shader(LEGACY_ROUNDED_FILL_FRAG)?;
|
||||
rounded_tex_vert_shader = self.create_shader(LEGACY_ROUNDED_TEX_VERT)?;
|
||||
rounded_tex_frag_shader = self.create_shader(LEGACY_ROUNDED_TEX_FRAG)?;
|
||||
out_vert_shader = None;
|
||||
out_frag_shader = None;
|
||||
tex_descriptor_set_layouts
|
||||
|
|
@ -400,6 +460,12 @@ impl VulkanDevice {
|
|||
tex_frag_shader,
|
||||
out_vert_shader,
|
||||
out_frag_shader,
|
||||
rounded_fill_vert_shader,
|
||||
rounded_fill_frag_shader,
|
||||
rounded_tex_vert_shader,
|
||||
rounded_tex_frag_shader,
|
||||
rounded_fill_pipelines: Default::default(),
|
||||
rounded_tex_pipelines: Default::default(),
|
||||
tex_descriptor_set_layouts,
|
||||
out_descriptor_set_layout,
|
||||
defunct: Cell::new(false),
|
||||
|
|
@ -457,6 +523,112 @@ impl VulkanRenderer {
|
|||
Ok(fill_pipelines)
|
||||
}
|
||||
|
||||
fn get_or_create_rounded_fill_pipelines(
|
||||
&self,
|
||||
format: vk::Format,
|
||||
) -> Result<FillPipelines, VulkanError> {
|
||||
if let Some(pl) = self.rounded_fill_pipelines.get(&format) {
|
||||
return Ok(pl);
|
||||
}
|
||||
let create_pipeline = |src_has_alpha| {
|
||||
let push_size = if self.device.descriptor_buffer.is_some() {
|
||||
size_of::<RoundedFillPushConstants>()
|
||||
} else {
|
||||
size_of::<LegacyRoundedFillPushConstants>()
|
||||
};
|
||||
let info = PipelineCreateInfo {
|
||||
format,
|
||||
vert: self.rounded_fill_vert_shader.clone(),
|
||||
frag: self.rounded_fill_frag_shader.clone(),
|
||||
blend: src_has_alpha,
|
||||
src_has_alpha,
|
||||
has_alpha_mult: false,
|
||||
alpha_mode: AlphaMode::PremultipliedOptical,
|
||||
eotf: EOTF_LINEAR,
|
||||
inv_eotf: EOTF_LINEAR,
|
||||
descriptor_set_layouts: Default::default(),
|
||||
has_color_management_data: false,
|
||||
};
|
||||
self.device.create_pipeline2(info, push_size)
|
||||
};
|
||||
let pipelines = Rc::new(static_map! {
|
||||
TexSourceType::HasAlpha => create_pipeline(true)?,
|
||||
TexSourceType::Opaque => create_pipeline(false)?,
|
||||
});
|
||||
self.rounded_fill_pipelines.set(format, pipelines.clone());
|
||||
Ok(pipelines)
|
||||
}
|
||||
|
||||
fn get_or_create_rounded_tex_pipelines(
|
||||
&self,
|
||||
format: vk::Format,
|
||||
target_cd: &ColorDescription,
|
||||
) -> Rc<TexPipelines> {
|
||||
let eotf = target_cd.eotf.to_vulkan();
|
||||
let pipelines = &self.rounded_tex_pipelines[eotf];
|
||||
match pipelines.get(&format) {
|
||||
Some(pl) => pl,
|
||||
_ => {
|
||||
let pl = Rc::new(TexPipelines {
|
||||
format,
|
||||
eotf,
|
||||
pipelines: Default::default(),
|
||||
});
|
||||
pipelines.set(format, pl.clone());
|
||||
pl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_create_rounded_tex_pipeline(
|
||||
&self,
|
||||
pipelines: &TexPipelines,
|
||||
tex_cd: &ColorDescription,
|
||||
tex_copy_type: TexCopyType,
|
||||
tex_source_type: TexSourceType,
|
||||
mut tex_alpha_mode: AlphaMode,
|
||||
has_color_management_data: bool,
|
||||
) -> Result<Rc<VulkanPipeline>, VulkanError> {
|
||||
if tex_source_type == TexSourceType::Opaque {
|
||||
tex_alpha_mode = AlphaMode::PremultipliedElectrical;
|
||||
}
|
||||
let key = TexPipelineKey {
|
||||
tex_copy_type,
|
||||
tex_source_type,
|
||||
tex_alpha_mode,
|
||||
eotf: tex_cd.eotf.to_vulkan(),
|
||||
has_color_management_data,
|
||||
};
|
||||
if let Some(pl) = pipelines.pipelines.get(&key) {
|
||||
return Ok(pl);
|
||||
}
|
||||
let has_alpha_mult = match tex_copy_type {
|
||||
TexCopyType::Identity => false,
|
||||
TexCopyType::Multiply => true,
|
||||
};
|
||||
let push_size = if self.device.descriptor_buffer.is_some() {
|
||||
size_of::<RoundedTexPushConstants>()
|
||||
} else {
|
||||
size_of::<LegacyRoundedTexPushConstants>()
|
||||
};
|
||||
let info = PipelineCreateInfo {
|
||||
format: pipelines.format,
|
||||
vert: self.rounded_tex_vert_shader.clone(),
|
||||
frag: self.rounded_tex_frag_shader.clone(),
|
||||
blend: true, // always blend since corners are transparent
|
||||
src_has_alpha: true, // rounding makes everything have alpha
|
||||
has_alpha_mult,
|
||||
alpha_mode: key.tex_alpha_mode,
|
||||
eotf: key.eotf.to_vulkan(),
|
||||
inv_eotf: pipelines.eotf.to_vulkan(),
|
||||
descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(),
|
||||
has_color_management_data,
|
||||
};
|
||||
let pl = self.device.create_pipeline2(info, push_size)?;
|
||||
pipelines.pipelines.set(key, pl.clone());
|
||||
Ok(pl)
|
||||
}
|
||||
|
||||
fn get_or_create_tex_pipelines(
|
||||
&self,
|
||||
format: vk::Format,
|
||||
|
|
@ -665,23 +837,34 @@ impl VulkanRenderer {
|
|||
RenderPass::FrameBuffer => fb_inv_eotf_args_descriptor,
|
||||
};
|
||||
for cmd in &mut memory.ops[pass] {
|
||||
let VulkanOp::Tex(c) = cmd else {
|
||||
continue;
|
||||
let (tex, resource_offset, cm_addr, eotf_addr) = match cmd {
|
||||
VulkanOp::Tex(c) => (
|
||||
&c.tex,
|
||||
&mut c.resource_descriptor_buffer_offset,
|
||||
&c.color_management_data_address,
|
||||
&c.eotf_args_address,
|
||||
),
|
||||
VulkanOp::RoundedTex(c) => (
|
||||
&c.tex,
|
||||
&mut c.resource_descriptor_buffer_offset,
|
||||
&c.color_management_data_address,
|
||||
&c.eotf_args_address,
|
||||
),
|
||||
_ => continue,
|
||||
};
|
||||
let tex = &c.tex;
|
||||
c.resource_descriptor_buffer_offset = resource_writer.next_offset();
|
||||
*resource_offset = resource_writer.next_offset();
|
||||
let mut writer = resource_writer.add_set(tex_descriptor_set_layout);
|
||||
writer.write(
|
||||
tex_descriptor_set_layout.offsets[0],
|
||||
tex.sampled_image_descriptor.as_ref().unwrap(),
|
||||
);
|
||||
if let Some(addr) = c.color_management_data_address {
|
||||
if let Some(addr) = *cm_addr {
|
||||
writer.write(
|
||||
tex_descriptor_set_layout.offsets[1],
|
||||
get_ub_descriptor!(addr, ColorManagementData),
|
||||
);
|
||||
}
|
||||
if let Some(addr) = c.eotf_args_address {
|
||||
if let Some(addr) = *eotf_addr {
|
||||
writer.write(
|
||||
tex_descriptor_set_layout.offsets[2],
|
||||
get_ub_descriptor!(addr, EotfArgs),
|
||||
|
|
@ -741,12 +924,19 @@ impl VulkanRenderer {
|
|||
enum Key {
|
||||
Fill { color: [u32; 4] },
|
||||
Tex(usize),
|
||||
RoundedFill { z_order: u32, color: [u32; 4] },
|
||||
RoundedTex(usize),
|
||||
}
|
||||
match o {
|
||||
VulkanOp::Fill(f) => Key::Fill {
|
||||
color: f.color.map(|c| c.to_bits()),
|
||||
},
|
||||
VulkanOp::Tex(t) => Key::Tex(t.index),
|
||||
VulkanOp::RoundedFill(f) => Key::RoundedFill {
|
||||
z_order: f.z_order,
|
||||
color: f.color.map(|c| c.to_bits()),
|
||||
},
|
||||
VulkanOp::RoundedTex(t) => Key::RoundedTex(t.index),
|
||||
}
|
||||
});
|
||||
let mops = &mut memory.ops[pass];
|
||||
|
|
@ -782,6 +972,22 @@ impl VulkanRenderer {
|
|||
}
|
||||
mops.push(VulkanOp::Tex(c));
|
||||
}
|
||||
VulkanOp::RoundedFill(mut f) => {
|
||||
f.range_address = memory.data_buffer.len() as DeviceAddress;
|
||||
memory.data_buffer.extend_from_slice(uapi::as_bytes(&f.target));
|
||||
mops.push(VulkanOp::RoundedFill(f));
|
||||
}
|
||||
VulkanOp::RoundedTex(mut c) => {
|
||||
c.range_address = memory.data_buffer.len() as DeviceAddress;
|
||||
let vertex = TexVertex {
|
||||
pos: c.target,
|
||||
tex_pos: c.source,
|
||||
};
|
||||
memory
|
||||
.data_buffer
|
||||
.extend_from_slice(uapi::as_bytes(&vertex));
|
||||
mops.push(VulkanOp::RoundedTex(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -917,6 +1123,106 @@ impl VulkanRenderer {
|
|||
}));
|
||||
}
|
||||
}
|
||||
GfxApiOpt::RoundedFillRect(rf) => {
|
||||
let target = rf.rect.to_points();
|
||||
for pass in RenderPass::variants() {
|
||||
let Some(bounds) = memory.paint_bounds[pass] else {
|
||||
continue;
|
||||
};
|
||||
if !bounds.intersects(&target) {
|
||||
continue;
|
||||
}
|
||||
let target_cd = match pass {
|
||||
RenderPass::BlendBuffer => blend_cd,
|
||||
RenderPass::FrameBuffer => fb_cd,
|
||||
};
|
||||
let tf = target_cd.eotf;
|
||||
let color = memory.color_transforms.apply_to_color(
|
||||
&rf.cd,
|
||||
target_cd,
|
||||
rf.render_intent,
|
||||
rf.color,
|
||||
);
|
||||
let color = color.to_array2(tf, rf.alpha);
|
||||
let source_type = TexSourceType::HasAlpha;
|
||||
memory.ops_tmp[pass].push(VulkanOp::RoundedFill(VulkanRoundedFillOp {
|
||||
target,
|
||||
color,
|
||||
source_type,
|
||||
size: rf.size,
|
||||
corner_radius: rf.corner_radius,
|
||||
border_width: rf.border_width,
|
||||
scale: rf.scale,
|
||||
range_address: 0,
|
||||
z_order: rf.z_order,
|
||||
}));
|
||||
}
|
||||
}
|
||||
GfxApiOpt::RoundedCopyTexture(ct) => {
|
||||
let tex = ct.tex.clone().into_vk(&self.device.device)?;
|
||||
if tex.contents_are_undefined.get() {
|
||||
log::warn!("Ignoring undefined texture");
|
||||
continue;
|
||||
}
|
||||
if tex.queue_state.get().acquire(QueueFamily::Gfx) == QueueTransfer::Impossible
|
||||
{
|
||||
log::warn!("Ignoring texture owned by different queue");
|
||||
continue;
|
||||
}
|
||||
let target = ct.target.to_points();
|
||||
let source = ct.source.to_points();
|
||||
for pass in RenderPass::variants() {
|
||||
let Some(bounds) = memory.paint_bounds[pass] else {
|
||||
continue;
|
||||
};
|
||||
if !bounds.intersects(&target) {
|
||||
continue;
|
||||
}
|
||||
let copy_type = match ct.alpha.is_some() {
|
||||
true => TexCopyType::Multiply,
|
||||
false => TexCopyType::Identity,
|
||||
};
|
||||
let source_type = TexSourceType::HasAlpha;
|
||||
let target_cd = match pass {
|
||||
RenderPass::BlendBuffer => blend_cd,
|
||||
RenderPass::FrameBuffer => fb_cd,
|
||||
};
|
||||
let color_management_data_address = memory.color_transforms.get_offset(
|
||||
&ct.cd.linear,
|
||||
target_cd,
|
||||
ct.render_intent,
|
||||
self.device.uniform_buffer_offset_mask,
|
||||
&mut memory.uniform_buffer_writer,
|
||||
);
|
||||
let eotf_args_address = memory.eotf_args_cache.get_offset(
|
||||
&ct.cd,
|
||||
false,
|
||||
self.device.uniform_buffer_offset_mask,
|
||||
&mut memory.uniform_buffer_writer,
|
||||
);
|
||||
memory.ops_tmp[pass].push(VulkanOp::RoundedTex(VulkanRoundedTexOp {
|
||||
tex: tex.clone(),
|
||||
index,
|
||||
target,
|
||||
source,
|
||||
buffer_resv: ct.buffer_resv.clone(),
|
||||
acquire_sync: Some(ct.acquire_sync.clone()),
|
||||
release_sync: ct.release_sync,
|
||||
alpha: ct.alpha.unwrap_or_default(),
|
||||
source_type,
|
||||
copy_type,
|
||||
alpha_mode: ct.alpha_mode,
|
||||
tex_cd: ct.cd.clone(),
|
||||
color_management_data_address,
|
||||
eotf_args_address,
|
||||
resource_descriptor_buffer_offset: 0,
|
||||
size: ct.size,
|
||||
corner_radius: ct.corner_radius,
|
||||
scale: ct.scale,
|
||||
range_address: 0,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sync(memory);
|
||||
|
|
@ -998,6 +1304,12 @@ impl VulkanRenderer {
|
|||
VulkanOp::Tex(c) => {
|
||||
c.range_address += buffer.buffer.address;
|
||||
}
|
||||
VulkanOp::RoundedFill(f) => {
|
||||
f.range_address += buffer.buffer.address;
|
||||
}
|
||||
VulkanOp::RoundedTex(c) => {
|
||||
c.range_address += buffer.buffer.address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1029,9 +1341,16 @@ impl VulkanRenderer {
|
|||
}
|
||||
for ops in memory.ops.values_mut() {
|
||||
for op in ops {
|
||||
if let VulkanOp::Tex(c) = op {
|
||||
adj!(&mut c.color_management_data_address);
|
||||
adj!(&mut c.eotf_args_address);
|
||||
match op {
|
||||
VulkanOp::Tex(c) => {
|
||||
adj!(&mut c.color_management_data_address);
|
||||
adj!(&mut c.eotf_args_address);
|
||||
}
|
||||
VulkanOp::RoundedTex(c) => {
|
||||
adj!(&mut c.color_management_data_address);
|
||||
adj!(&mut c.eotf_args_address);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1051,8 +1370,12 @@ impl VulkanRenderer {
|
|||
let execution = self.allocate_point();
|
||||
for pass in RenderPass::variants() {
|
||||
for cmd in &mut memory.ops[pass] {
|
||||
if let VulkanOp::Tex(c) = cmd {
|
||||
let tex = &c.tex;
|
||||
let tex_data = match cmd {
|
||||
VulkanOp::Tex(c) => Some((&c.tex, &mut c.buffer_resv, &mut c.acquire_sync, c.release_sync)),
|
||||
VulkanOp::RoundedTex(c) => Some((&c.tex, &mut c.buffer_resv, &mut c.acquire_sync, c.release_sync)),
|
||||
_ => None,
|
||||
};
|
||||
if let Some((tex, buffer_resv, acquire_sync, release_sync)) = tex_data {
|
||||
if tex.execution_version.replace(execution) == execution {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1066,9 +1389,9 @@ impl VulkanRenderer {
|
|||
}
|
||||
memory.textures.push(UsedTexture {
|
||||
tex: tex.clone(),
|
||||
resv: c.buffer_resv.take(),
|
||||
acquire_sync: c.acquire_sync.take().unwrap(),
|
||||
release_sync: c.release_sync,
|
||||
resv: buffer_resv.take(),
|
||||
acquire_sync: acquire_sync.take().unwrap(),
|
||||
release_sync,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1301,6 +1624,10 @@ impl VulkanRenderer {
|
|||
let memory = &*self.memory.borrow();
|
||||
let fill_pl = self.get_or_create_fill_pipelines(target.format.vk_format)?;
|
||||
let tex_pl = self.get_or_create_tex_pipelines(target.format.vk_format, target_cd);
|
||||
let rounded_fill_pl =
|
||||
self.get_or_create_rounded_fill_pipelines(target.format.vk_format)?;
|
||||
let rounded_tex_pl =
|
||||
self.get_or_create_rounded_tex_pipelines(target.format.vk_format, target_cd);
|
||||
let dev = &self.device.device;
|
||||
let mut current_pipeline = None;
|
||||
let mut bind = |pipeline: &VulkanPipeline| {
|
||||
|
|
@ -1421,6 +1748,134 @@ impl VulkanRenderer {
|
|||
}
|
||||
}
|
||||
}
|
||||
VulkanOp::RoundedFill(r) => {
|
||||
let pipeline = &rounded_fill_pl[r.source_type];
|
||||
bind(pipeline);
|
||||
if self.device.descriptor_buffer.is_some() {
|
||||
let push = RoundedFillPushConstants {
|
||||
color: r.color,
|
||||
vertices: r.range_address,
|
||||
size: r.size,
|
||||
corner_radius: r.corner_radius,
|
||||
border_width: r.border_width,
|
||||
scale: r.scale,
|
||||
};
|
||||
unsafe {
|
||||
dev.cmd_push_constants(
|
||||
buf,
|
||||
pipeline.pipeline_layout,
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||
0,
|
||||
uapi::as_bytes(&push),
|
||||
);
|
||||
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||
}
|
||||
} else {
|
||||
let push = LegacyRoundedFillPushConstants {
|
||||
pos: r.target,
|
||||
color: r.color,
|
||||
size_x: r.size[0],
|
||||
size_y: r.size[1],
|
||||
corner_radius_tl: r.corner_radius[0],
|
||||
corner_radius_tr: r.corner_radius[1],
|
||||
corner_radius_br: r.corner_radius[2],
|
||||
corner_radius_bl: r.corner_radius[3],
|
||||
border_width: r.border_width,
|
||||
scale: r.scale,
|
||||
};
|
||||
unsafe {
|
||||
dev.cmd_push_constants(
|
||||
buf,
|
||||
pipeline.pipeline_layout,
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||
0,
|
||||
uapi::as_bytes(&push),
|
||||
);
|
||||
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
VulkanOp::RoundedTex(c) => {
|
||||
let tex = &c.tex;
|
||||
let pipeline = self.get_or_create_rounded_tex_pipeline(
|
||||
&rounded_tex_pl,
|
||||
&c.tex_cd,
|
||||
c.copy_type,
|
||||
c.source_type,
|
||||
c.alpha_mode,
|
||||
c.color_management_data_address.is_some(),
|
||||
)?;
|
||||
bind(&pipeline);
|
||||
let image_info = DescriptorImageInfo::default()
|
||||
.image_view(tex.texture_view)
|
||||
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
|
||||
if let Some(db) = &self.device.descriptor_buffer {
|
||||
let push = RoundedTexPushConstants {
|
||||
vertices: c.range_address,
|
||||
alpha: c.alpha,
|
||||
size_x: c.size[0],
|
||||
size_y: c.size[1],
|
||||
corner_radius_tl: c.corner_radius[0],
|
||||
corner_radius_tr: c.corner_radius[1],
|
||||
corner_radius_br: c.corner_radius[2],
|
||||
corner_radius_bl: c.corner_radius[3],
|
||||
scale: c.scale,
|
||||
};
|
||||
unsafe {
|
||||
db.cmd_set_descriptor_buffer_offsets(
|
||||
buf,
|
||||
PipelineBindPoint::GRAPHICS,
|
||||
pipeline.pipeline_layout,
|
||||
0,
|
||||
&[0, 1],
|
||||
&[0, c.resource_descriptor_buffer_offset],
|
||||
);
|
||||
dev.cmd_push_constants(
|
||||
buf,
|
||||
pipeline.pipeline_layout,
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||
0,
|
||||
uapi::as_bytes(&push),
|
||||
);
|
||||
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||
}
|
||||
} else {
|
||||
let write_descriptor_set = WriteDescriptorSet::default()
|
||||
.descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.image_info(slice::from_ref(&image_info));
|
||||
unsafe {
|
||||
self.device.push_descriptor.cmd_push_descriptor_set(
|
||||
buf,
|
||||
PipelineBindPoint::GRAPHICS,
|
||||
pipeline.pipeline_layout,
|
||||
0,
|
||||
slice::from_ref(&write_descriptor_set),
|
||||
);
|
||||
}
|
||||
let push = LegacyRoundedTexPushConstants {
|
||||
pos: c.target,
|
||||
tex_pos: c.source,
|
||||
alpha: c.alpha,
|
||||
size_x: c.size[0],
|
||||
size_y: c.size[1],
|
||||
corner_radius_tl: c.corner_radius[0],
|
||||
corner_radius_tr: c.corner_radius[1],
|
||||
corner_radius_br: c.corner_radius[2],
|
||||
corner_radius_bl: c.corner_radius[3],
|
||||
scale: c.scale,
|
||||
};
|
||||
unsafe {
|
||||
dev.cmd_push_constants(
|
||||
buf,
|
||||
pipeline.pipeline_layout,
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||
0,
|
||||
uapi::as_bytes(&push),
|
||||
);
|
||||
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -1933,6 +2388,14 @@ impl VulkanRenderer {
|
|||
};
|
||||
(opaque, c.target)
|
||||
}
|
||||
GfxApiOpt::RoundedFillRect(rf) => {
|
||||
// Rounded rects are never fully opaque due to AA at corners,
|
||||
// but they do paint pixels and need paint regions.
|
||||
(false, rf.rect)
|
||||
}
|
||||
GfxApiOpt::RoundedCopyTexture(ct) => {
|
||||
(false, ct.target)
|
||||
}
|
||||
};
|
||||
if opaque || bb.is_none() {
|
||||
tag |= 1;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,18 @@ pub const LEGACY_FILL_VERT: &[u8] = include_bytes!("shaders_bin/legacy_fill.vert
|
|||
pub const LEGACY_FILL_FRAG: &[u8] = include_bytes!("shaders_bin/legacy_fill.frag.spv");
|
||||
pub const LEGACY_TEX_VERT: &[u8] = include_bytes!("shaders_bin/legacy_tex.vert.spv");
|
||||
pub const LEGACY_TEX_FRAG: &[u8] = include_bytes!("shaders_bin/legacy_tex.frag.spv");
|
||||
pub const ROUNDED_FILL_VERT: &[u8] = include_bytes!("shaders_bin/rounded_fill.vert.spv");
|
||||
pub const ROUNDED_FILL_FRAG: &[u8] = include_bytes!("shaders_bin/rounded_fill.frag.spv");
|
||||
pub const ROUNDED_TEX_VERT: &[u8] = include_bytes!("shaders_bin/rounded_tex.vert.spv");
|
||||
pub const ROUNDED_TEX_FRAG: &[u8] = include_bytes!("shaders_bin/rounded_tex.frag.spv");
|
||||
pub const LEGACY_ROUNDED_FILL_VERT: &[u8] =
|
||||
include_bytes!("shaders_bin/legacy_rounded_fill.vert.spv");
|
||||
pub const LEGACY_ROUNDED_FILL_FRAG: &[u8] =
|
||||
include_bytes!("shaders_bin/legacy_rounded_fill.frag.spv");
|
||||
pub const LEGACY_ROUNDED_TEX_VERT: &[u8] =
|
||||
include_bytes!("shaders_bin/legacy_rounded_tex.vert.spv");
|
||||
pub const LEGACY_ROUNDED_TEX_FRAG: &[u8] =
|
||||
include_bytes!("shaders_bin/legacy_rounded_tex.frag.spv");
|
||||
|
||||
pub struct VulkanShader {
|
||||
pub(super) device: Rc<VulkanDevice>,
|
||||
|
|
@ -99,6 +111,69 @@ pub struct LegacyTexPushConstants {
|
|||
|
||||
unsafe impl Packed for LegacyTexPushConstants {}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct RoundedFillPushConstants {
|
||||
pub color: [f32; 4],
|
||||
pub vertices: DeviceAddress,
|
||||
pub size: [f32; 2],
|
||||
pub corner_radius: [f32; 4],
|
||||
pub border_width: f32,
|
||||
pub scale: f32,
|
||||
}
|
||||
|
||||
unsafe impl Packed for RoundedFillPushConstants {}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct LegacyRoundedFillPushConstants {
|
||||
pub pos: [[f32; 2]; 4],
|
||||
pub color: [f32; 4],
|
||||
pub size_x: f32,
|
||||
pub size_y: f32,
|
||||
pub corner_radius_tl: f32,
|
||||
pub corner_radius_tr: f32,
|
||||
pub corner_radius_br: f32,
|
||||
pub corner_radius_bl: f32,
|
||||
pub border_width: f32,
|
||||
pub scale: f32,
|
||||
}
|
||||
|
||||
unsafe impl Packed for LegacyRoundedFillPushConstants {}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct RoundedTexPushConstants {
|
||||
pub vertices: DeviceAddress,
|
||||
pub alpha: f32,
|
||||
pub size_x: f32,
|
||||
pub size_y: f32,
|
||||
pub corner_radius_tl: f32,
|
||||
pub corner_radius_tr: f32,
|
||||
pub corner_radius_br: f32,
|
||||
pub corner_radius_bl: f32,
|
||||
pub scale: f32,
|
||||
}
|
||||
|
||||
unsafe impl Packed for RoundedTexPushConstants {}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct LegacyRoundedTexPushConstants {
|
||||
pub pos: [[f32; 2]; 4],
|
||||
pub tex_pos: [[f32; 2]; 4],
|
||||
pub alpha: f32,
|
||||
pub size_x: f32,
|
||||
pub size_y: f32,
|
||||
pub corner_radius_tl: f32,
|
||||
pub corner_radius_tr: f32,
|
||||
pub corner_radius_br: f32,
|
||||
pub corner_radius_bl: f32,
|
||||
pub scale: f32,
|
||||
}
|
||||
|
||||
unsafe impl Packed for LegacyRoundedTexPushConstants {}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct OutPushConstants {
|
||||
|
|
|
|||
12
src/gfx_apis/vulkan/shaders/legacy/rounded_fill.common.glsl
Normal file
12
src/gfx_apis/vulkan/shaders/legacy/rounded_fill.common.glsl
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
layout(push_constant, std430) uniform Data {
|
||||
layout(offset = 0) vec2 pos[4];
|
||||
layout(offset = 32) vec4 color;
|
||||
layout(offset = 48) float size_x;
|
||||
layout(offset = 52) float size_y;
|
||||
layout(offset = 56) float corner_radius_tl;
|
||||
layout(offset = 60) float corner_radius_tr;
|
||||
layout(offset = 64) float corner_radius_br;
|
||||
layout(offset = 68) float corner_radius_bl;
|
||||
layout(offset = 72) float border_width;
|
||||
layout(offset = 76) float scale;
|
||||
} data;
|
||||
48
src/gfx_apis/vulkan/shaders/legacy/rounded_fill.frag
Normal file
48
src/gfx_apis/vulkan/shaders/legacy/rounded_fill.frag
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#version 450
|
||||
|
||||
#include "../frag_spec_const.glsl"
|
||||
#include "rounded_fill.common.glsl"
|
||||
|
||||
layout(location = 0) in vec2 geo_pos;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
|
||||
if (coords.x < 0.0 || coords.y < 0.0 || coords.x > size.x || coords.y > size.y) {
|
||||
return 0.0;
|
||||
}
|
||||
vec2 center;
|
||||
float radius;
|
||||
if (coords.x < corner_radius.x && coords.y < corner_radius.x) {
|
||||
radius = corner_radius.x;
|
||||
center = vec2(radius, radius);
|
||||
} else if (coords.x > size.x - corner_radius.y && coords.y < corner_radius.y) {
|
||||
radius = corner_radius.y;
|
||||
center = vec2(size.x - radius, radius);
|
||||
} else if (coords.x > size.x - corner_radius.z && coords.y > size.y - corner_radius.z) {
|
||||
radius = corner_radius.z;
|
||||
center = vec2(size.x - radius, size.y - radius);
|
||||
} else if (coords.x < corner_radius.w && coords.y > size.y - corner_radius.w) {
|
||||
radius = corner_radius.w;
|
||||
center = vec2(radius, size.y - radius);
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
float half_px = 0.5 / data.scale;
|
||||
float dist = distance(coords, center);
|
||||
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 size = vec2(data.size_x, data.size_y);
|
||||
vec4 corner_radius = vec4(data.corner_radius_tl, data.corner_radius_tr, data.corner_radius_br, data.corner_radius_bl);
|
||||
float outer_alpha = rounding_alpha(geo_pos, size, corner_radius);
|
||||
float alpha = outer_alpha;
|
||||
if (data.border_width > 0.0) {
|
||||
vec2 inner_coords = geo_pos - vec2(data.border_width);
|
||||
vec2 inner_size = size - vec2(2.0 * data.border_width);
|
||||
vec4 inner_radius = max(corner_radius - vec4(data.border_width), vec4(0.0));
|
||||
float inner_alpha = rounding_alpha(inner_coords, inner_size, inner_radius);
|
||||
alpha = outer_alpha * (1.0 - inner_alpha);
|
||||
}
|
||||
out_color = data.color * alpha;
|
||||
}
|
||||
17
src/gfx_apis/vulkan/shaders/legacy/rounded_fill.vert
Normal file
17
src/gfx_apis/vulkan/shaders/legacy/rounded_fill.vert
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#version 450
|
||||
|
||||
#include "rounded_fill.common.glsl"
|
||||
|
||||
layout(location = 0) out vec2 geo_pos;
|
||||
|
||||
void main() {
|
||||
vec2 size = vec2(data.size_x, data.size_y);
|
||||
vec2 pos;
|
||||
switch (gl_VertexIndex) {
|
||||
case 0: pos = data.pos[0]; geo_pos = vec2(size.x, 0.0); break;
|
||||
case 1: pos = data.pos[1]; geo_pos = vec2(0.0, 0.0); break;
|
||||
case 2: pos = data.pos[2]; geo_pos = vec2(size.x, size.y); break;
|
||||
case 3: pos = data.pos[3]; geo_pos = vec2(0.0, size.y); break;
|
||||
}
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
}
|
||||
12
src/gfx_apis/vulkan/shaders/legacy/rounded_tex.common.glsl
Normal file
12
src/gfx_apis/vulkan/shaders/legacy/rounded_tex.common.glsl
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
layout(push_constant, std430) uniform Data {
|
||||
layout(offset = 0) vec2 pos[4];
|
||||
layout(offset = 32) vec2 tex_pos[4];
|
||||
layout(offset = 64) float mul;
|
||||
layout(offset = 68) float size_x;
|
||||
layout(offset = 72) float size_y;
|
||||
layout(offset = 76) float corner_radius_tl;
|
||||
layout(offset = 80) float corner_radius_tr;
|
||||
layout(offset = 84) float corner_radius_br;
|
||||
layout(offset = 88) float corner_radius_bl;
|
||||
layout(offset = 92) float scale;
|
||||
} data;
|
||||
51
src/gfx_apis/vulkan/shaders/legacy/rounded_tex.frag
Normal file
51
src/gfx_apis/vulkan/shaders/legacy/rounded_tex.frag
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#version 450
|
||||
|
||||
#include "../frag_spec_const.glsl"
|
||||
#include "rounded_tex.common.glsl"
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D tex;
|
||||
layout(location = 0) in vec2 tex_pos;
|
||||
layout(location = 1) in vec2 geo_pos;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
|
||||
if (coords.x < 0.0 || coords.y < 0.0 || coords.x > size.x || coords.y > size.y) {
|
||||
return 0.0;
|
||||
}
|
||||
vec2 center;
|
||||
float radius;
|
||||
if (coords.x < corner_radius.x && coords.y < corner_radius.x) {
|
||||
radius = corner_radius.x;
|
||||
center = vec2(radius, radius);
|
||||
} else if (coords.x > size.x - corner_radius.y && coords.y < corner_radius.y) {
|
||||
radius = corner_radius.y;
|
||||
center = vec2(size.x - radius, radius);
|
||||
} else if (coords.x > size.x - corner_radius.z && coords.y > size.y - corner_radius.z) {
|
||||
radius = corner_radius.z;
|
||||
center = vec2(size.x - radius, size.y - radius);
|
||||
} else if (coords.x < corner_radius.w && coords.y > size.y - corner_radius.w) {
|
||||
radius = corner_radius.w;
|
||||
center = vec2(radius, size.y - radius);
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
float half_px = 0.5 / data.scale;
|
||||
float dist = distance(coords, center);
|
||||
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 size = vec2(data.size_x, data.size_y);
|
||||
vec4 corner_radius = vec4(data.corner_radius_tl, data.corner_radius_tr, data.corner_radius_br, data.corner_radius_bl);
|
||||
vec4 c = textureLod(tex, tex_pos, 0);
|
||||
if (has_alpha_multiplier) {
|
||||
if (src_has_alpha) {
|
||||
c *= data.mul;
|
||||
} else {
|
||||
c = vec4(c.rgb * data.mul, data.mul);
|
||||
}
|
||||
}
|
||||
float ra = rounding_alpha(geo_pos, size, corner_radius);
|
||||
c *= ra;
|
||||
out_color = c;
|
||||
}
|
||||
18
src/gfx_apis/vulkan/shaders/legacy/rounded_tex.vert
Normal file
18
src/gfx_apis/vulkan/shaders/legacy/rounded_tex.vert
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#version 450
|
||||
|
||||
#include "rounded_tex.common.glsl"
|
||||
|
||||
layout(location = 0) out vec2 tex_pos;
|
||||
layout(location = 1) out vec2 geo_pos;
|
||||
|
||||
void main() {
|
||||
vec2 size = vec2(data.size_x, data.size_y);
|
||||
vec2 pos;
|
||||
switch (gl_VertexIndex) {
|
||||
case 0: pos = data.pos[0]; tex_pos = data.tex_pos[0]; geo_pos = vec2(size.x, 0.0); break;
|
||||
case 1: pos = data.pos[1]; tex_pos = data.tex_pos[1]; geo_pos = vec2(0.0, 0.0); break;
|
||||
case 2: pos = data.pos[2]; tex_pos = data.tex_pos[2]; geo_pos = vec2(size.x, size.y); break;
|
||||
case 3: pos = data.pos[3]; tex_pos = data.tex_pos[3]; geo_pos = vec2(0.0, size.y); break;
|
||||
}
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
}
|
||||
14
src/gfx_apis/vulkan/shaders/rounded_fill.common.glsl
Normal file
14
src/gfx_apis/vulkan/shaders/rounded_fill.common.glsl
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#extension GL_EXT_buffer_reference : require
|
||||
|
||||
layout(buffer_reference, buffer_reference_align = 8, std430) readonly buffer Vertices {
|
||||
vec2 pos[][4];
|
||||
};
|
||||
|
||||
layout(push_constant, std430) uniform Data {
|
||||
vec4 color;
|
||||
Vertices vertices;
|
||||
vec2 size;
|
||||
vec4 corner_radius;
|
||||
float border_width;
|
||||
float scale;
|
||||
} data;
|
||||
45
src/gfx_apis/vulkan/shaders/rounded_fill.frag
Normal file
45
src/gfx_apis/vulkan/shaders/rounded_fill.frag
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#version 450
|
||||
|
||||
#include "rounded_fill.common.glsl"
|
||||
|
||||
layout(location = 0) in vec2 geo_pos;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
|
||||
if (coords.x < 0.0 || coords.y < 0.0 || coords.x > size.x || coords.y > size.y) {
|
||||
return 0.0;
|
||||
}
|
||||
vec2 center;
|
||||
float radius;
|
||||
if (coords.x < corner_radius.x && coords.y < corner_radius.x) {
|
||||
radius = corner_radius.x;
|
||||
center = vec2(radius, radius);
|
||||
} else if (coords.x > size.x - corner_radius.y && coords.y < corner_radius.y) {
|
||||
radius = corner_radius.y;
|
||||
center = vec2(size.x - radius, radius);
|
||||
} else if (coords.x > size.x - corner_radius.z && coords.y > size.y - corner_radius.z) {
|
||||
radius = corner_radius.z;
|
||||
center = vec2(size.x - radius, size.y - radius);
|
||||
} else if (coords.x < corner_radius.w && coords.y > size.y - corner_radius.w) {
|
||||
radius = corner_radius.w;
|
||||
center = vec2(radius, size.y - radius);
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
float half_px = 0.5 / data.scale;
|
||||
float dist = distance(coords, center);
|
||||
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float outer_alpha = rounding_alpha(geo_pos, data.size, data.corner_radius);
|
||||
float alpha = outer_alpha;
|
||||
if (data.border_width > 0.0) {
|
||||
vec2 inner_coords = geo_pos - vec2(data.border_width);
|
||||
vec2 inner_size = data.size - vec2(2.0 * data.border_width);
|
||||
vec4 inner_radius = max(data.corner_radius - vec4(data.border_width), vec4(0.0));
|
||||
float inner_alpha = rounding_alpha(inner_coords, inner_size, inner_radius);
|
||||
alpha = outer_alpha * (1.0 - inner_alpha);
|
||||
}
|
||||
out_color = data.color * alpha;
|
||||
}
|
||||
16
src/gfx_apis/vulkan/shaders/rounded_fill.vert
Normal file
16
src/gfx_apis/vulkan/shaders/rounded_fill.vert
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#version 450
|
||||
|
||||
#include "rounded_fill.common.glsl"
|
||||
|
||||
layout(location = 0) out vec2 geo_pos;
|
||||
|
||||
void main() {
|
||||
vec2 pos = data.vertices.pos[gl_InstanceIndex][gl_VertexIndex];
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
switch (gl_VertexIndex) {
|
||||
case 0: geo_pos = vec2(data.size.x, 0.0); break;
|
||||
case 1: geo_pos = vec2(0.0, 0.0); break;
|
||||
case 2: geo_pos = vec2(data.size.x, data.size.y); break;
|
||||
case 3: geo_pos = vec2(0.0, data.size.y); break;
|
||||
}
|
||||
}
|
||||
22
src/gfx_apis/vulkan/shaders/rounded_tex.common.glsl
Normal file
22
src/gfx_apis/vulkan/shaders/rounded_tex.common.glsl
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#extension GL_EXT_buffer_reference : require
|
||||
|
||||
struct Vertex {
|
||||
vec2 pos[4];
|
||||
vec2 tex_pos[4];
|
||||
};
|
||||
|
||||
layout(buffer_reference, buffer_reference_align = 8, std430) readonly buffer Vertices {
|
||||
Vertex vertices[];
|
||||
};
|
||||
|
||||
layout(push_constant, std430) uniform Data {
|
||||
layout(offset = 0) Vertices vertices;
|
||||
layout(offset = 8) float mul;
|
||||
layout(offset = 12) float size_x;
|
||||
layout(offset = 16) float size_y;
|
||||
layout(offset = 20) float corner_radius_tl;
|
||||
layout(offset = 24) float corner_radius_tr;
|
||||
layout(offset = 28) float corner_radius_br;
|
||||
layout(offset = 32) float corner_radius_bl;
|
||||
layout(offset = 36) float scale;
|
||||
} data;
|
||||
76
src/gfx_apis/vulkan/shaders/rounded_tex.frag
Normal file
76
src/gfx_apis/vulkan/shaders/rounded_tex.frag
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#version 450
|
||||
|
||||
#extension GL_EXT_scalar_block_layout : require
|
||||
|
||||
#define TEX_SET 1
|
||||
|
||||
#include "frag_spec_const.glsl"
|
||||
#include "rounded_tex.common.glsl"
|
||||
#include "tex_set.glsl"
|
||||
#include "eotfs.glsl"
|
||||
#include "alpha_modes.glsl"
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler sam;
|
||||
layout(location = 0) in vec2 tex_pos;
|
||||
layout(location = 1) in vec2 geo_pos;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
|
||||
if (coords.x < 0.0 || coords.y < 0.0 || coords.x > size.x || coords.y > size.y) {
|
||||
return 0.0;
|
||||
}
|
||||
vec2 center;
|
||||
float radius;
|
||||
if (coords.x < corner_radius.x && coords.y < corner_radius.x) {
|
||||
radius = corner_radius.x;
|
||||
center = vec2(radius, radius);
|
||||
} else if (coords.x > size.x - corner_radius.y && coords.y < corner_radius.y) {
|
||||
radius = corner_radius.y;
|
||||
center = vec2(size.x - radius, radius);
|
||||
} else if (coords.x > size.x - corner_radius.z && coords.y > size.y - corner_radius.z) {
|
||||
radius = corner_radius.z;
|
||||
center = vec2(size.x - radius, size.y - radius);
|
||||
} else if (coords.x < corner_radius.w && coords.y > size.y - corner_radius.w) {
|
||||
radius = corner_radius.w;
|
||||
center = vec2(radius, size.y - radius);
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
float half_px = 0.5 / data.scale;
|
||||
float dist = distance(coords, center);
|
||||
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 size = vec2(data.size_x, data.size_y);
|
||||
vec4 corner_radius = vec4(data.corner_radius_tl, data.corner_radius_tr, data.corner_radius_br, data.corner_radius_bl);
|
||||
vec4 c = textureLod(sampler2D(tex, sam), tex_pos, 0);
|
||||
if (eotf != inv_eotf || has_matrix || alpha_mode != AM_PREMULTIPLIED_ELECTRICAL) {
|
||||
vec3 rgb = c.rgb;
|
||||
if (src_has_alpha && alpha_mode == AM_PREMULTIPLIED_ELECTRICAL) {
|
||||
rgb /= mix(c.a, 1.0, c.a == 0.0);
|
||||
}
|
||||
rgb = apply_eotf(rgb);
|
||||
if (src_has_alpha && alpha_mode == AM_PREMULTIPLIED_OPTICAL) {
|
||||
rgb /= mix(c.a, 1.0, c.a == 0.0);
|
||||
}
|
||||
if (has_matrix) {
|
||||
rgb = (cm_data.matrix * vec4(rgb, 1.0)).rgb;
|
||||
}
|
||||
rgb = apply_inv_eotf(rgb);
|
||||
if (src_has_alpha) {
|
||||
rgb *= c.a;
|
||||
}
|
||||
c.rgb = rgb;
|
||||
}
|
||||
if (has_alpha_multiplier) {
|
||||
if (src_has_alpha) {
|
||||
c *= data.mul;
|
||||
} else {
|
||||
c = vec4(c.rgb * data.mul, data.mul);
|
||||
}
|
||||
}
|
||||
float ra = rounding_alpha(geo_pos, size, corner_radius);
|
||||
c *= ra;
|
||||
out_color = c;
|
||||
}
|
||||
19
src/gfx_apis/vulkan/shaders/rounded_tex.vert
Normal file
19
src/gfx_apis/vulkan/shaders/rounded_tex.vert
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#version 450
|
||||
|
||||
#include "rounded_tex.common.glsl"
|
||||
|
||||
layout(location = 0) out vec2 tex_pos;
|
||||
layout(location = 1) out vec2 geo_pos;
|
||||
|
||||
void main() {
|
||||
vec2 size = vec2(data.size_x, data.size_y);
|
||||
Vertex vertex = data.vertices.vertices[gl_InstanceIndex];
|
||||
gl_Position = vec4(vertex.pos[gl_VertexIndex], 0.0, 1.0);
|
||||
tex_pos = vertex.tex_pos[gl_VertexIndex];
|
||||
switch (gl_VertexIndex) {
|
||||
case 0: geo_pos = vec2(size.x, 0.0); break;
|
||||
case 1: geo_pos = vec2(0.0, 0.0); break;
|
||||
case 2: geo_pos = vec2(size.x, size.y); break;
|
||||
case 3: geo_pos = vec2(0.0, size.y); break;
|
||||
}
|
||||
}
|
||||
BIN
src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.frag.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.frag.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.vert.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/legacy_rounded_fill.vert.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.frag.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.frag.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.vert.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/legacy_rounded_tex.vert.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/rounded_fill.frag.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/rounded_fill.frag.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/rounded_fill.vert.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/rounded_fill.vert.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/rounded_tex.frag.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/rounded_tex.frag.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/rounded_tex.vert.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/rounded_tex.vert.spv
Normal file
Binary file not shown.
|
|
@ -7,12 +7,24 @@ f93524fd077bc9984702b1e0f92232f80bfe28a0a92439dc164c1ea41fd16d64 src/gfx_apis/vu
|
|||
c315a064b48dd5bdb607a6b79c30d31b6e59ffec69e93d50ab875abf97c41bbf src/gfx_apis/vulkan/shaders/legacy/fill.common.glsl
|
||||
590d061b97446fc501158609eaf098b71bc7b328c008b586ff36613ce690d618 src/gfx_apis/vulkan/shaders/legacy/fill.frag
|
||||
ad22a79e1a88a12daa40c0a2b953084c129a408297c8ca544d60e0b6001470b9 src/gfx_apis/vulkan/shaders/legacy/fill.vert
|
||||
b77838c0aac9ec90ae76cd0d94d3891d72d9a30b09ce77009afd9f4e567dd042 src/gfx_apis/vulkan/shaders/legacy/rounded_fill.common.glsl
|
||||
fa39734aea1c96960f5dc95b999ae2fa5576ecf4b527fd70ee0f643c8ddcc452 src/gfx_apis/vulkan/shaders/legacy/rounded_fill.frag
|
||||
c1914cc00fb4827f65cd55bd0737d159fe44a098a3085a500822fc91cc2bfcad src/gfx_apis/vulkan/shaders/legacy/rounded_fill.vert
|
||||
bd249cf170b72cd833e92a7719e88da0a91e563956579707e693679b443d73d5 src/gfx_apis/vulkan/shaders/legacy/rounded_tex.common.glsl
|
||||
28f3249e0d974a332b2926fb7565930627a093d6ac21ca17f2bf191740d299bd src/gfx_apis/vulkan/shaders/legacy/rounded_tex.frag
|
||||
6ef0bde549dc163cd08f68d975071f5d74213c07ccc4a06b30c6f179b2f848ae src/gfx_apis/vulkan/shaders/legacy/rounded_tex.vert
|
||||
e0a8769dd7938dd02e66db9e9048ed6bef8f8c42671f2e2c7a7976a6d498f685 src/gfx_apis/vulkan/shaders/legacy/tex.common.glsl
|
||||
0e7c72ea11671065842c8b4ad4131a7df33b427dc0ea76bf5a896546f6636cb0 src/gfx_apis/vulkan/shaders/legacy/tex.frag
|
||||
4402f7ccdbb9fb52fb6cda3aab13cf89e2980c79b541f8be0463efd64a5f98ed src/gfx_apis/vulkan/shaders/legacy/tex.vert
|
||||
3ba5d05c2b95099e5424b3ade5d1c31d431f5730b1d0b51a9fb5f8afc4ea14b4 src/gfx_apis/vulkan/shaders/out.common.glsl
|
||||
5069f619c7d722815a022e2d84720a2d8290af49a3ed49ea0cd26b52115cc39a src/gfx_apis/vulkan/shaders/out.frag
|
||||
0adc7e12328c15fb3e7e6c8b8701a182223c2f15337e14131f41dd247e697809 src/gfx_apis/vulkan/shaders/out.vert
|
||||
9202d5c9fc4ce0d5f40ed147f245bd037728c9e060ea46a0f0a1767ca55e6c48 src/gfx_apis/vulkan/shaders/rounded_fill.common.glsl
|
||||
9085625d2afb1365685ae79a58108bf6566573ed94d9913397cf74dc6ef9b6e8 src/gfx_apis/vulkan/shaders/rounded_fill.frag
|
||||
7665319a706e514f125d80f51f10b643f01cdae54d8a6ea56c218f78de7c0ecb src/gfx_apis/vulkan/shaders/rounded_fill.vert
|
||||
dd100d048c0b380c913cffd7ac48fed3a341b3cb052302a11c369967f38aba9a src/gfx_apis/vulkan/shaders/rounded_tex.common.glsl
|
||||
454f34754ea4102190821c2d168dedd8c6bf624f1712b6136d902428f801a1e9 src/gfx_apis/vulkan/shaders/rounded_tex.frag
|
||||
21b18ba369b505b9aedb8cf2e7e31bc417f6704fd2daac353b0db52f9ae44c70 src/gfx_apis/vulkan/shaders/rounded_tex.vert
|
||||
e22d4d3318a350def8ef19c7b27dc6a308a84c2fe9d7c02b81107f72073cd481 src/gfx_apis/vulkan/shaders/tex.common.glsl
|
||||
1f196cee646a934072beb3e5648a5042c035953d9a0c26b0a22e330c2f8bb994 src/gfx_apis/vulkan/shaders/tex.frag
|
||||
423cf327c9fcc4070dbf75321c1224a1589b6cf3d2f1ea5e8bd0362e1a9f3aa1 src/gfx_apis/vulkan/shaders/tex.vert
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#![allow(clippy::excessive_precision)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use {
|
||||
crate::{
|
||||
|
|
@ -22,12 +23,14 @@ pub struct Icons {
|
|||
icons: CopyHashMap<i32, Option<Rc<SizedIcons>>>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug, Linearize)]
|
||||
pub enum IconState {
|
||||
Active,
|
||||
Passive,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct SizedIcons {
|
||||
pub pin_unfocused_title: StaticMap<IconState, Rc<dyn GfxTexture>>,
|
||||
pub pin_focused_title: StaticMap<IconState, Rc<dyn GfxTexture>>,
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ use {
|
|||
rect::Rect,
|
||||
state::{DeviceHandlerData, State},
|
||||
tree::{
|
||||
ContainerNode, ContainerSplit, Direction, FoundNode, Node, NodeId, NodeLayer,
|
||||
ContainerNode, ContainerSplit, ChangeGroupAction, Direction, FoundNode, Node, NodeId, NodeLayer,
|
||||
NodeLayerLink, NodeLocation, OutputNode, StackedNode, ToplevelNode, WorkspaceNode,
|
||||
generic_node_visitor, toplevel_create_split, toplevel_parent_container,
|
||||
toplevel_set_floating, toplevel_set_workspace,
|
||||
|
|
@ -745,6 +745,40 @@ impl WlSeatGlobal {
|
|||
toplevel_create_split(&self.state, tl, axis);
|
||||
}
|
||||
|
||||
pub fn toggle_tab(&self) {
|
||||
if let Some(c) = self.kb_parent_container() {
|
||||
c.change_group(ChangeGroupAction::ToggleTab);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_group(&self, axis: ContainerSplit, ephemeral: bool) {
|
||||
if let Some(c) = self.kb_parent_container() {
|
||||
c.make_group(axis, ephemeral);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_group_opposite(&self) {
|
||||
if let Some(c) = self.kb_parent_container() {
|
||||
c.change_group(ChangeGroupAction::Opposite);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn equalize(&self, recursive: bool) {
|
||||
if let Some(c) = self.kb_parent_container() {
|
||||
if recursive {
|
||||
c.equalize_recursive();
|
||||
} else {
|
||||
c.equalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_tab(&self, right: bool) {
|
||||
if let Some(c) = self.kb_parent_container() {
|
||||
c.move_tab(right);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus_parent(self: &Rc<Self>) {
|
||||
if let Some(tl) = self.keyboard_node.get().node_toplevel()
|
||||
&& let Some(parent) = tl.tl_data().parent.get()
|
||||
|
|
@ -1320,6 +1354,7 @@ impl WlSeatGlobal {
|
|||
.start_drag(self, origin, source, icon, serial)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn start_tile_drag(self: &Rc<Self>, tl: &Rc<dyn ToplevelNode>) {
|
||||
if self.state.ui_drag_enabled.get() {
|
||||
self.pointer_owner.start_tile_drag(self, tl);
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ impl PointerOwnerHolder {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn start_tile_drag(&self, seat: &Rc<WlSeatGlobal>, tl: &Rc<dyn ToplevelNode>) {
|
||||
self.owner.get().start_tile_drag(seat, tl);
|
||||
}
|
||||
|
|
@ -288,6 +289,7 @@ trait PointerOwner {
|
|||
fn disable_window_management(&self, seat: &Rc<WlSeatGlobal>) {
|
||||
let _ = seat;
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
fn start_tile_drag(&self, seat: &Rc<WlSeatGlobal>, tl: &Rc<dyn ToplevelNode>) {
|
||||
let _ = seat;
|
||||
let _ = tl;
|
||||
|
|
@ -732,6 +734,7 @@ trait SimplePointerOwnerUsecase: Sized + Clone + 'static {
|
|||
let _ = seat;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn start_tile_drag(
|
||||
&self,
|
||||
grab: &SimpleGrabPointerOwner<Self>,
|
||||
|
|
|
|||
|
|
@ -528,6 +528,8 @@ impl GfxFramebuffer for TestGfxFb {
|
|||
GfxApiOpt::Sync => {}
|
||||
GfxApiOpt::FillRect(f) => fill_rect(&f, staging),
|
||||
GfxApiOpt::CopyTexture(c) => copy_texture(&c, staging)?,
|
||||
GfxApiOpt::RoundedFillRect(_) => {}
|
||||
GfxApiOpt::RoundedCopyTexture(_) => {}
|
||||
}
|
||||
}
|
||||
copy_from_staging(staging);
|
||||
|
|
|
|||
483
src/renderer.rs
483
src/renderer.rs
|
|
@ -2,7 +2,6 @@ use {
|
|||
crate::{
|
||||
cmm::cmm_render_intent::RenderIntent,
|
||||
gfx_api::{AcquireSync, AlphaMode, GfxApiOpt, ReleaseSync, SampleRect},
|
||||
icons::{IconState, SizedIcons},
|
||||
ifs::wl_surface::{
|
||||
SurfaceBuffer, WlSurface,
|
||||
x_surface::xwindow::Xwindow,
|
||||
|
|
@ -13,10 +12,10 @@ use {
|
|||
renderer::renderer_base::RendererBase,
|
||||
scale::Scale,
|
||||
state::State,
|
||||
theme::Color,
|
||||
theme::{Color, CornerRadius},
|
||||
tree::{
|
||||
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
|
||||
ToplevelNodeBase, WorkspaceNode,
|
||||
ToplevelNodeBase, WorkspaceNode, tab_bar::TabBar,
|
||||
},
|
||||
},
|
||||
std::{ops::Deref, rc::Rc, slice},
|
||||
|
|
@ -29,8 +28,8 @@ pub struct Renderer<'a> {
|
|||
pub state: &'a State,
|
||||
pub logical_extents: Rect,
|
||||
pub pixel_extents: Rect,
|
||||
pub icons: Option<Rc<SizedIcons>>,
|
||||
pub stretch: Option<(i32, i32)>,
|
||||
pub corner_radius: Option<CornerRadius>,
|
||||
}
|
||||
|
||||
impl Renderer<'_> {
|
||||
|
|
@ -278,68 +277,126 @@ impl Renderer<'_> {
|
|||
self.render_tl_aux(placeholder.tl_data(), bounds, true);
|
||||
}
|
||||
|
||||
fn render_container_decorations(&mut self, container: &ContainerNode, x: i32, y: 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_srgb.linear;
|
||||
let perceptual = RenderIntent::Perceptual;
|
||||
let rd = container.render_data.borrow_mut();
|
||||
let c = self.state.theme.colors.unfocused_title_background.get();
|
||||
self.base
|
||||
.fill_boxes2(&rd.title_rects, &c, srgb, perceptual, x, y);
|
||||
let c = self.state.theme.colors.focused_title_background.get();
|
||||
self.base
|
||||
.fill_boxes2(&rd.active_title_rects, &c, srgb, perceptual, x, y);
|
||||
let c = self.state.theme.colors.attention_requested_background.get();
|
||||
self.base
|
||||
.fill_boxes2(&rd.attention_title_rects, &c, srgb, perceptual, x, y);
|
||||
let c = self.state.theme.colors.separator.get();
|
||||
self.base
|
||||
.fill_boxes2(&rd.underline_rects, &c, srgb, perceptual, x, y);
|
||||
let c = self.state.theme.colors.border.get();
|
||||
self.base
|
||||
.fill_boxes2(&rd.border_rects, &c, srgb, perceptual, x, y);
|
||||
if let Some(lar) = &rd.last_active_rect {
|
||||
let c = self
|
||||
.state
|
||||
.theme
|
||||
.colors
|
||||
.focused_inactive_title_background
|
||||
.get();
|
||||
let scalef = self.base.scalef as f32;
|
||||
|
||||
let radius = self.state.theme.sizes.tab_bar_radius.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 bar_height = tab_bar.height;
|
||||
let render_scale = tab_bar.render_scale;
|
||||
|
||||
// Vulkan sorts ops: Fill < Tex < RoundedFill (by z_order, color) < RoundedTex.
|
||||
// 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_cr = CornerRadius::from(radius as f32);
|
||||
|
||||
// Tiny FillRect strip to establish Vulkan paint regions (visually hidden
|
||||
// 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(std::slice::from_ref(lar), &c, srgb, perceptual, x, y);
|
||||
}
|
||||
if let Some(titles) = rd.titles.get(&self.base.scale) {
|
||||
for title in titles {
|
||||
let rect = title.rect.move_(x, y);
|
||||
let bounds = self.base.scale_rect(rect);
|
||||
let (x, y) = self.base.scale_point(rect.x1(), rect.y1());
|
||||
self.base.render_texture(
|
||||
&title.tex,
|
||||
.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),
|
||||
&bg_color,
|
||||
None,
|
||||
srgb,
|
||||
perceptual,
|
||||
tab_cr.scaled_by(scalef),
|
||||
0.0,
|
||||
0,
|
||||
);
|
||||
|
||||
// Rounded border ring on top (z_order=1, renders after bg).
|
||||
if border_width > 0 {
|
||||
self.base.fill_rounded_rect_z(
|
||||
tab_rect.move_(x, y),
|
||||
&border_color,
|
||||
None,
|
||||
x,
|
||||
y,
|
||||
srgb,
|
||||
perceptual,
|
||||
tab_cr.scaled_by(scalef),
|
||||
border_width as f32 * scalef,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
// Title text as RoundedCopyTexture (sorts after all RoundedFill).
|
||||
let tex_ref = entry.title_texture.borrow();
|
||||
if let Some(tex) = tex_ref.as_ref()
|
||||
&& let Some(texture) = tex.texture()
|
||||
{
|
||||
use crate::theme::TabTitleAlign;
|
||||
let (tw, _th) = texture.size();
|
||||
let tex_width = (tw as f64 / render_scale.to_f64()).round() as i32;
|
||||
let tab_inner = ew - 2 * (text_padding + border_width);
|
||||
let text_x = match self.state.theme.tab_title_align.get() {
|
||||
TabTitleAlign::Start => x + ex + text_padding + border_width,
|
||||
TabTitleAlign::Center => {
|
||||
x + ex + border_width + (tab_inner.max(0) - tex_width).max(0) / 2 + text_padding.min(tab_inner.max(0) / 2)
|
||||
}
|
||||
TabTitleAlign::End => {
|
||||
let end_x = x + ex + ew - tex_width - text_padding - border_width;
|
||||
end_x.max(x + ex + border_width)
|
||||
}
|
||||
};
|
||||
let (tx, ty) = self.base.scale_point(text_x, y);
|
||||
self.base.render_rounded_texture(
|
||||
&texture,
|
||||
None,
|
||||
tx,
|
||||
ty,
|
||||
None,
|
||||
None,
|
||||
self.base.scale,
|
||||
Some(&bounds),
|
||||
render_scale,
|
||||
None,
|
||||
None,
|
||||
AcquireSync::None,
|
||||
ReleaseSync::None,
|
||||
false,
|
||||
srgb_srgb,
|
||||
self.state.color_manager.srgb_gamma22(),
|
||||
perceptual,
|
||||
AlphaMode::PremultipliedElectrical,
|
||||
CornerRadius::from(0.0_f32),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
|
||||
let floating = self.state.theme.floating_titles.get();
|
||||
fn render_container_decorations(&mut self, container: &ContainerNode, x: i32, y: i32) {
|
||||
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
||||
let srgb = &srgb_srgb.linear;
|
||||
let perceptual = RenderIntent::Perceptual;
|
||||
let rd = container.render_data.borrow_mut();
|
||||
let c = self.state.theme.colors.border.get();
|
||||
self.base
|
||||
.fill_boxes2(&rd.border_rects, &c, srgb, perceptual, x, y);
|
||||
}
|
||||
|
||||
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
|
||||
self.render_container_decorations(container, x, y);
|
||||
|
||||
if let Some(child) = container.mono_child.get() {
|
||||
// Render tab bar if present.
|
||||
{
|
||||
let tab_bar = container.tab_bar.borrow();
|
||||
if let Some(tb) = tab_bar.as_ref() {
|
||||
self.render_tab_bar(tb, x, y, container.width.get());
|
||||
}
|
||||
}
|
||||
let mb = container.mono_body.get();
|
||||
if self.state.theme.sizes.gap.get() != 0 {
|
||||
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
||||
|
|
@ -355,44 +412,37 @@ impl Renderer<'_> {
|
|||
let full_w = mb.width();
|
||||
let srgb = &srgb_srgb.linear;
|
||||
let perceptual = RenderIntent::Perceptual;
|
||||
if floating {
|
||||
if !child.node.node_is_container() {
|
||||
let body_frame = [
|
||||
Rect::new_sized_saturating(mb.x1() - bw, mb.y1(), bw, mb.y2() - mb.y1()),
|
||||
Rect::new_sized_saturating(mb.x2(), mb.y1(), bw, mb.y2() - mb.y1()),
|
||||
Rect::new_sized_saturating(mb.x1() - bw, mb.y1() - bw, full_w + 2 * bw, bw),
|
||||
Rect::new_sized_saturating(mb.x1() - bw, mb.y2(), full_w + 2 * bw, bw),
|
||||
if !child.node.node_is_container() {
|
||||
let cr = self.state.theme.corner_radius.get();
|
||||
let frame_y = mb.y1();
|
||||
let frame_h = mb.height();
|
||||
if cr.is_zero() {
|
||||
let frame_rects = [
|
||||
Rect::new_sized_saturating(mb.x1() - bw, frame_y, bw, frame_h),
|
||||
Rect::new_sized_saturating(mb.x2(), frame_y, bw, frame_h),
|
||||
Rect::new_sized_saturating(mb.x1() - bw, frame_y - bw, full_w + 2 * bw, bw),
|
||||
Rect::new_sized_saturating(mb.x1() - bw, frame_y + frame_h, full_w + 2 * bw, bw),
|
||||
];
|
||||
self.base.fill_boxes2(&body_frame, c, srgb, perceptual, x, y);
|
||||
self.base.fill_boxes2(&frame_rects, c, srgb, perceptual, x, y);
|
||||
} else {
|
||||
let outer = Rect::new_sized_saturating(
|
||||
mb.x1() - bw,
|
||||
frame_y - bw,
|
||||
full_w + 2 * bw,
|
||||
frame_h + 2 * bw,
|
||||
);
|
||||
let scalef = self.base.scalef as f32;
|
||||
let scaled_cr = cr.scaled_by(scalef);
|
||||
self.base.fill_rounded_rect(
|
||||
outer.move_(x, y),
|
||||
c,
|
||||
None,
|
||||
srgb,
|
||||
perceptual,
|
||||
scaled_cr,
|
||||
bw as f32 * scalef,
|
||||
);
|
||||
}
|
||||
let th = self.state.theme.title_height();
|
||||
if th > 0 {
|
||||
for tab in container.children.iter() {
|
||||
let tr = tab.title_rect.get();
|
||||
let tc = if tab.active.get() {
|
||||
&focused_border_color
|
||||
} else {
|
||||
&border_color
|
||||
};
|
||||
let tw = tr.width();
|
||||
let tab_frame = [
|
||||
Rect::new_sized_saturating(tr.x1() - bw, tr.y1() - bw, tw + 2 * bw, bw),
|
||||
Rect::new_sized_saturating(tr.x1() - bw, tr.y1(), bw, th),
|
||||
Rect::new_sized_saturating(tr.x2(), tr.y1(), bw, th),
|
||||
Rect::new_sized_saturating(tr.x1() - bw, tr.y1() + th, tw + 2 * bw, bw),
|
||||
];
|
||||
self.base.fill_boxes2(&tab_frame, tc, srgb, perceptual, x, y);
|
||||
}
|
||||
}
|
||||
} else if !child.node.node_is_container() {
|
||||
let full_h = mb.y2();
|
||||
let frame_rects = [
|
||||
Rect::new_sized_saturating(mb.x1() - bw, 0, bw, full_h),
|
||||
Rect::new_sized_saturating(mb.x2(), 0, bw, full_h),
|
||||
Rect::new_sized_saturating(mb.x1() - bw, -bw, full_w + 2 * bw, bw),
|
||||
Rect::new_sized_saturating(mb.x1() - bw, full_h, full_w + 2 * bw, bw),
|
||||
];
|
||||
self.base.fill_boxes2(&frame_rects, c, srgb, perceptual, x, y);
|
||||
}
|
||||
}
|
||||
let body = mb.move_(x, y);
|
||||
|
|
@ -403,28 +453,36 @@ impl Renderer<'_> {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
if self.state.theme.sizes.gap.get() != 0 && !child.node.node_is_container() {
|
||||
let cr = self.state.theme.corner_radius.get();
|
||||
if !cr.is_zero() {
|
||||
let scalef = self.base.scalef as f32;
|
||||
let bw = self.state.theme.sizes.border_width.get();
|
||||
let inner_cr = cr.expanded_by(-(bw as f32)).scaled_by(scalef);
|
||||
self.corner_radius = Some(inner_cr);
|
||||
}
|
||||
}
|
||||
child
|
||||
.node
|
||||
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
||||
self.stretch = None;
|
||||
} else {
|
||||
let gap = self.state.theme.sizes.gap.get();
|
||||
let (srgb_srgb, bw, border_color, focused_border_color, tpuh) = if gap != 0 {
|
||||
let (srgb_srgb, bw, border_color, focused_border_color) = if gap != 0 {
|
||||
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
||||
let bw = self.state.theme.sizes.border_width.get();
|
||||
let border_color = self.state.theme.colors.border.get();
|
||||
let focused_border_color = self.state.theme.colors.focused_title_background.get();
|
||||
let tpuh = self.state.theme.title_plus_underline_height();
|
||||
(
|
||||
Some(srgb_srgb),
|
||||
bw,
|
||||
border_color,
|
||||
focused_border_color,
|
||||
tpuh,
|
||||
)
|
||||
} else {
|
||||
(None, 0, Color::SOLID_BLACK, Color::SOLID_BLACK, 0)
|
||||
(None, 0, Color::SOLID_BLACK, Color::SOLID_BLACK)
|
||||
};
|
||||
let cr = self.state.theme.corner_radius.get();
|
||||
for child in container.children.iter() {
|
||||
let body = child.body.get();
|
||||
if body.x1() >= container.width.get() || body.y1() >= container.height.get() {
|
||||
|
|
@ -432,45 +490,42 @@ impl Renderer<'_> {
|
|||
}
|
||||
if let Some(srgb_srgb) = srgb_srgb {
|
||||
let srgb = &srgb_srgb.linear;
|
||||
let c = if floating {
|
||||
if child.active.get() { &focused_border_color } else { &border_color }
|
||||
} else if child.border_color_is_focused.get() {
|
||||
let c = if child.border_color_is_focused.get() {
|
||||
&focused_border_color
|
||||
} else {
|
||||
&border_color
|
||||
};
|
||||
let title_rect = child.title_rect.get();
|
||||
let full_w = body.width();
|
||||
let perceptual = RenderIntent::Perceptual;
|
||||
if floating && tpuh > 0 {
|
||||
let tw = title_rect.width();
|
||||
let title_h = title_rect.height();
|
||||
let title_frame = [
|
||||
Rect::new_sized_saturating(title_rect.x1() - bw, title_rect.y1() - bw, tw + 2 * bw, bw),
|
||||
Rect::new_sized_saturating(title_rect.x1() - bw, title_rect.y1(), bw, title_h),
|
||||
Rect::new_sized_saturating(title_rect.x2(), title_rect.y1(), bw, title_h),
|
||||
Rect::new_sized_saturating(title_rect.x1() - bw, title_rect.y1() + title_h, tw + 2 * bw, bw),
|
||||
];
|
||||
self.base.fill_boxes2(&title_frame, c, srgb, perceptual, x, y);
|
||||
if !child.node.node_is_container() && gap != 0 {
|
||||
let body_frame = [
|
||||
Rect::new_sized_saturating(body.x1() - bw, body.y1(), bw, body.y2() - body.y1()),
|
||||
Rect::new_sized_saturating(body.x2(), body.y1(), bw, body.y2() - body.y1()),
|
||||
if !child.node.node_is_container() && gap != 0 {
|
||||
let full_w = body.width();
|
||||
let perceptual = RenderIntent::Perceptual;
|
||||
let full_h = body.height();
|
||||
if cr.is_zero() {
|
||||
let frame_rects = [
|
||||
Rect::new_sized_saturating(body.x1() - bw, body.y1(), bw, full_h),
|
||||
Rect::new_sized_saturating(body.x2(), body.y1(), bw, full_h),
|
||||
Rect::new_sized_saturating(body.x1() - bw, body.y1() - bw, full_w + 2 * bw, bw),
|
||||
Rect::new_sized_saturating(body.x1() - bw, body.y2(), full_w + 2 * bw, bw),
|
||||
];
|
||||
self.base.fill_boxes2(&body_frame, c, srgb, perceptual, x, y);
|
||||
self.base.fill_boxes2(&frame_rects, c, srgb, perceptual, x, y);
|
||||
} else {
|
||||
let outer = Rect::new_sized_saturating(
|
||||
body.x1() - bw,
|
||||
body.y1() - bw,
|
||||
full_w + 2 * bw,
|
||||
full_h + 2 * bw,
|
||||
);
|
||||
let scalef = self.base.scalef as f32;
|
||||
let scaled_cr = cr.scaled_by(scalef);
|
||||
self.base.fill_rounded_rect(
|
||||
outer.move_(x, y),
|
||||
c,
|
||||
None,
|
||||
srgb,
|
||||
perceptual,
|
||||
scaled_cr,
|
||||
bw as f32 * scalef,
|
||||
);
|
||||
}
|
||||
} else if !child.node.node_is_container() && gap != 0 {
|
||||
let top_y = if tpuh > 0 { title_rect.y1() } else { body.y1() };
|
||||
let full_h = body.y2() - top_y;
|
||||
let frame_rects = [
|
||||
Rect::new_sized_saturating(body.x1() - bw, top_y, bw, full_h),
|
||||
Rect::new_sized_saturating(body.x2(), top_y, bw, full_h),
|
||||
Rect::new_sized_saturating(body.x1() - bw, top_y - bw, full_w + 2 * bw, bw),
|
||||
Rect::new_sized_saturating(body.x1() - bw, body.y2(), full_w + 2 * bw, bw),
|
||||
];
|
||||
self.base.fill_boxes2(&frame_rects, c, srgb, perceptual, x, y);
|
||||
}
|
||||
}
|
||||
let content = child.content.get();
|
||||
|
|
@ -479,6 +534,11 @@ impl Renderer<'_> {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
if !cr.is_zero() && !child.node.node_is_container() && gap != 0 {
|
||||
let scalef = self.base.scalef as f32;
|
||||
let inner_cr = cr.expanded_by(-(bw as f32)).scaled_by(scalef);
|
||||
self.corner_radius = Some(inner_cr);
|
||||
}
|
||||
let body = body.move_(x, y);
|
||||
let body = self.base.scale_rect(body);
|
||||
child
|
||||
|
|
@ -642,28 +702,49 @@ impl Renderer<'_> {
|
|||
let cd = surface.color_description();
|
||||
let intent = surface.render_intent();
|
||||
let alpha_mode = surface.alpha_mode();
|
||||
let corner_radius = self.corner_radius.take();
|
||||
if let Some(tex) = buf.get_texture(surface) {
|
||||
let mut opaque = surface.opaque();
|
||||
if !opaque && tex.format().has_alpha {
|
||||
opaque = self.bounds_are_opaque(x, y, bounds, surface);
|
||||
if let Some(cr) = corner_radius {
|
||||
self.base.render_rounded_texture(
|
||||
&tex,
|
||||
alpha,
|
||||
x,
|
||||
y,
|
||||
Some(tpoints),
|
||||
Some(tsize),
|
||||
self.base.scale,
|
||||
bounds,
|
||||
Some(buffer.clone()),
|
||||
AcquireSync::Unnecessary,
|
||||
buffer.release_sync,
|
||||
&cd,
|
||||
intent,
|
||||
alpha_mode,
|
||||
cr,
|
||||
);
|
||||
} else {
|
||||
let mut opaque = surface.opaque();
|
||||
if !opaque && tex.format().has_alpha {
|
||||
opaque = self.bounds_are_opaque(x, y, bounds, surface);
|
||||
}
|
||||
self.base.render_texture(
|
||||
&tex,
|
||||
alpha,
|
||||
x,
|
||||
y,
|
||||
Some(tpoints),
|
||||
Some(tsize),
|
||||
self.base.scale,
|
||||
bounds,
|
||||
Some(buffer.clone()),
|
||||
AcquireSync::Unnecessary,
|
||||
buffer.release_sync,
|
||||
opaque,
|
||||
&cd,
|
||||
intent,
|
||||
alpha_mode,
|
||||
);
|
||||
}
|
||||
self.base.render_texture(
|
||||
&tex,
|
||||
alpha,
|
||||
x,
|
||||
y,
|
||||
Some(tpoints),
|
||||
Some(tsize),
|
||||
self.base.scale,
|
||||
bounds,
|
||||
Some(buffer.clone()),
|
||||
AcquireSync::Unnecessary,
|
||||
buffer.release_sync,
|
||||
opaque,
|
||||
&cd,
|
||||
intent,
|
||||
alpha_mode,
|
||||
);
|
||||
} else if let Some(color) = &buf.color {
|
||||
if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) {
|
||||
let rect = match bounds {
|
||||
|
|
@ -691,128 +772,60 @@ impl Renderer<'_> {
|
|||
};
|
||||
let pos = floating.position.get();
|
||||
let theme = &self.state.theme;
|
||||
let th = theme.title_height();
|
||||
let tpuh = theme.title_plus_underline_height();
|
||||
let tuh = theme.title_underline_height();
|
||||
let bw = theme.sizes.border_width.get();
|
||||
let bc = if floating.active.get() {
|
||||
theme.colors.focused_title_background.get()
|
||||
} else {
|
||||
theme.colors.border.get()
|
||||
};
|
||||
let tc = if floating.active.get() {
|
||||
theme.colors.focused_title_background.get()
|
||||
} else if floating.attention_requested.get() {
|
||||
theme.colors.attention_requested_background.get()
|
||||
} else {
|
||||
theme.colors.unfocused_title_background.get()
|
||||
};
|
||||
let uc = theme.colors.separator.get();
|
||||
let gap = theme.sizes.gap.get();
|
||||
let floating_title = theme.floating_titles.get() && gap != 0;
|
||||
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
||||
let srgb = &srgb_srgb.linear;
|
||||
let perceptual = RenderIntent::Perceptual;
|
||||
if floating_title {
|
||||
let title_frame = [
|
||||
Rect::new_sized_saturating(x, y, pos.width(), bw),
|
||||
Rect::new_sized_saturating(x, y + bw, bw, th),
|
||||
Rect::new_sized_saturating(x + pos.width() - bw, y + bw, bw, th),
|
||||
Rect::new_sized_saturating(x, y + bw + th, pos.width(), bw),
|
||||
];
|
||||
self.base.fill_boxes(&title_frame, &bc, srgb, perceptual);
|
||||
let title_bg = [Rect::new_sized_saturating(x + bw, y + bw, pos.width() - 2 * bw, th)];
|
||||
self.base.fill_boxes(&title_bg, &tc, srgb, perceptual);
|
||||
let body_top = y + tpuh;
|
||||
let body_inner_top = y + bw + tpuh;
|
||||
let body_inner_height = pos.height() - 2 * bw - tpuh;
|
||||
let body_frame = [
|
||||
Rect::new_sized_saturating(x, body_top, pos.width(), bw),
|
||||
Rect::new_sized_saturating(x, body_inner_top, bw, body_inner_height),
|
||||
Rect::new_sized_saturating(x + pos.width() - bw, body_inner_top, bw, body_inner_height),
|
||||
Rect::new_sized_saturating(x, y + pos.height() - bw, pos.width(), bw),
|
||||
];
|
||||
self.base.fill_boxes(&body_frame, &bc, srgb, perceptual);
|
||||
} else {
|
||||
let cr = theme.corner_radius.get();
|
||||
if cr.is_zero() {
|
||||
let borders = [
|
||||
Rect::new_sized_saturating(x, y, pos.width(), bw),
|
||||
Rect::new_sized_saturating(x, y + bw, bw, pos.height() - bw),
|
||||
Rect::new_sized_saturating(x + pos.width() - bw, y + bw, bw, pos.height() - bw),
|
||||
Rect::new_sized_saturating(x + bw, y + pos.height() - bw, pos.width() - 2 * bw, bw),
|
||||
Rect::new_sized_saturating(
|
||||
x + pos.width() - bw,
|
||||
y + bw,
|
||||
bw,
|
||||
pos.height() - bw,
|
||||
),
|
||||
Rect::new_sized_saturating(
|
||||
x + bw,
|
||||
y + pos.height() - bw,
|
||||
pos.width() - 2 * bw,
|
||||
bw,
|
||||
),
|
||||
];
|
||||
self.base.fill_boxes(&borders, &bc, srgb, perceptual);
|
||||
let title = [Rect::new_sized_saturating(x + bw, y + bw, pos.width() - 2 * bw, th)];
|
||||
self.base.fill_boxes(&title, &tc, srgb, perceptual);
|
||||
let title_underline = [Rect::new_sized_saturating(x + bw, y + bw + th, pos.width() - 2 * bw, tuh)];
|
||||
self.base.fill_boxes(&title_underline, &uc, srgb, perceptual);
|
||||
}
|
||||
let rect = floating.title_rect.get().move_(x, y);
|
||||
let bounds = self.base.scale_rect(rect);
|
||||
let (mut x1, y1) = rect.position();
|
||||
let is_pinned = floating.pinned_link.borrow().is_some();
|
||||
if is_pinned || self.state.show_pin_icon.get() {
|
||||
let (x, y) = self.base.scale_point(x1, y1);
|
||||
if let Some(icons) = &self.icons {
|
||||
let icon = if floating.active.get() {
|
||||
&icons.pin_focused_title
|
||||
} else if floating.attention_requested.get() {
|
||||
&icons.pin_attention_requested
|
||||
} else {
|
||||
&icons.pin_unfocused_title
|
||||
};
|
||||
let state = match is_pinned {
|
||||
true => IconState::Active,
|
||||
false => IconState::Passive,
|
||||
};
|
||||
self.base.render_texture(
|
||||
&icon[state],
|
||||
None,
|
||||
x,
|
||||
y,
|
||||
None,
|
||||
None,
|
||||
self.base.scale,
|
||||
Some(&bounds),
|
||||
None,
|
||||
AcquireSync::None,
|
||||
ReleaseSync::None,
|
||||
false,
|
||||
srgb_srgb,
|
||||
perceptual,
|
||||
AlphaMode::PremultipliedElectrical,
|
||||
);
|
||||
}
|
||||
x1 += th;
|
||||
}
|
||||
if let Some(title) = floating.title_textures.borrow().get(&self.base.scale)
|
||||
&& let Some(texture) = title.texture()
|
||||
{
|
||||
let (x, y) = self.base.scale_point(x1, y1);
|
||||
self.base.render_texture(
|
||||
&texture,
|
||||
} else {
|
||||
let outer = Rect::new_sized_saturating(x, y, pos.width(), pos.height());
|
||||
let scalef = self.base.scalef as f32;
|
||||
let scaled_cr = cr.scaled_by(scalef);
|
||||
self.base.fill_rounded_rect(
|
||||
outer,
|
||||
&bc,
|
||||
None,
|
||||
x,
|
||||
y,
|
||||
None,
|
||||
None,
|
||||
self.base.scale,
|
||||
Some(&bounds),
|
||||
None,
|
||||
AcquireSync::None,
|
||||
ReleaseSync::None,
|
||||
false,
|
||||
srgb_srgb,
|
||||
srgb,
|
||||
perceptual,
|
||||
AlphaMode::PremultipliedElectrical,
|
||||
scaled_cr,
|
||||
bw as f32 * scalef,
|
||||
);
|
||||
}
|
||||
let body = Rect::new_sized_saturating(
|
||||
x + bw,
|
||||
y + bw + tpuh,
|
||||
y + bw,
|
||||
pos.width() - 2 * bw,
|
||||
pos.height() - 2 * bw - tpuh,
|
||||
pos.height() - 2 * bw,
|
||||
);
|
||||
let scissor_body = self.base.scale_rect(body);
|
||||
if !cr.is_zero() {
|
||||
let scalef = self.base.scalef as f32;
|
||||
let inner_cr = cr.expanded_by(-(bw as f32)).scaled_by(scalef);
|
||||
self.corner_radius = Some(inner_cr);
|
||||
}
|
||||
child.node_render(self, body.x1(), body.y1(), Some(&scissor_body));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ use {
|
|||
},
|
||||
gfx_api::{
|
||||
AcquireSync, AlphaMode, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt,
|
||||
GfxTexture, ReleaseSync, SampleRect,
|
||||
GfxTexture, ReleaseSync, RoundedCopyTexture, RoundedFillRect, SampleRect,
|
||||
},
|
||||
rect::Rect,
|
||||
scale::Scale,
|
||||
theme::Color,
|
||||
theme::{Color, CornerRadius},
|
||||
tree::Transform,
|
||||
},
|
||||
std::rc::Rc,
|
||||
|
|
@ -250,6 +250,138 @@ impl RendererBase<'_> {
|
|||
}));
|
||||
}
|
||||
|
||||
pub fn fill_rounded_rect(
|
||||
&mut self,
|
||||
rect: Rect,
|
||||
color: &Color,
|
||||
alpha: Option<f32>,
|
||||
cd: &Rc<LinearColorDescription>,
|
||||
render_intent: RenderIntent,
|
||||
corner_radius: CornerRadius,
|
||||
border_width: f32,
|
||||
) {
|
||||
self.fill_rounded_rect_z(rect, color, alpha, cd, render_intent, corner_radius, border_width, 0)
|
||||
}
|
||||
|
||||
pub fn fill_rounded_rect_z(
|
||||
&mut self,
|
||||
rect: Rect,
|
||||
color: &Color,
|
||||
alpha: Option<f32>,
|
||||
cd: &Rc<LinearColorDescription>,
|
||||
render_intent: RenderIntent,
|
||||
corner_radius: CornerRadius,
|
||||
border_width: f32,
|
||||
z_order: u32,
|
||||
) {
|
||||
if *color == Color::TRANSPARENT {
|
||||
return;
|
||||
}
|
||||
let rect = self.scale_rect(rect);
|
||||
let width = (rect.x2() - rect.x1()) as f32;
|
||||
let height = (rect.y2() - rect.y1()) as f32;
|
||||
let scale = self.scalef as f32;
|
||||
let fitted = corner_radius.fit_to(width, height);
|
||||
let cr: [f32; 4] = fitted.into();
|
||||
self.ops
|
||||
.push(GfxApiOpt::RoundedFillRect(RoundedFillRect {
|
||||
rect: FramebufferRect::new(
|
||||
rect.x1() as f32,
|
||||
rect.y1() as f32,
|
||||
rect.x2() as f32,
|
||||
rect.y2() as f32,
|
||||
self.transform,
|
||||
self.fb_width,
|
||||
self.fb_height,
|
||||
),
|
||||
color: *color,
|
||||
alpha,
|
||||
render_intent,
|
||||
cd: cd.clone(),
|
||||
size: [width, height],
|
||||
corner_radius: cr,
|
||||
border_width,
|
||||
scale,
|
||||
z_order,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn render_rounded_texture(
|
||||
&mut self,
|
||||
texture: &Rc<dyn GfxTexture>,
|
||||
alpha: Option<f32>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
tpoints: Option<SampleRect>,
|
||||
tsize: Option<(i32, i32)>,
|
||||
tscale: Scale,
|
||||
bounds: Option<&Rect>,
|
||||
buffer_resv: Option<Rc<dyn BufferResv>>,
|
||||
acquire_sync: AcquireSync,
|
||||
release_sync: ReleaseSync,
|
||||
cd: &Rc<ColorDescription>,
|
||||
render_intent: RenderIntent,
|
||||
alpha_mode: AlphaMode,
|
||||
corner_radius: CornerRadius,
|
||||
) {
|
||||
let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity);
|
||||
|
||||
let (twidth, theight) = if let Some(size) = tsize {
|
||||
size
|
||||
} else {
|
||||
let (mut w, mut h) = texcoord.buffer_transform.maybe_swap(texture.size());
|
||||
if tscale != self.scale {
|
||||
let tscale = tscale.to_f64();
|
||||
w = (w as f64 * self.scalef / tscale).round() as _;
|
||||
h = (h as f64 * self.scalef / tscale).round() as _;
|
||||
}
|
||||
(w, h)
|
||||
};
|
||||
|
||||
let mut target_x = [x, x + twidth];
|
||||
let mut target_y = [y, y + theight];
|
||||
|
||||
if let Some(bounds) = bounds
|
||||
&& bound_target(&mut target_x, &mut target_y, &mut texcoord, bounds)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let target = FramebufferRect::new(
|
||||
target_x[0] as f32,
|
||||
target_y[0] as f32,
|
||||
target_x[1] as f32,
|
||||
target_y[1] as f32,
|
||||
self.transform,
|
||||
self.fb_width,
|
||||
self.fb_height,
|
||||
);
|
||||
|
||||
let width = (target_x[1] - target_x[0]) as f32;
|
||||
let height = (target_y[1] - target_y[0]) as f32;
|
||||
let scale = self.scalef as f32;
|
||||
let fitted = corner_radius.fit_to(width, height);
|
||||
let cr: [f32; 4] = fitted.into();
|
||||
|
||||
self.ops
|
||||
.push(GfxApiOpt::RoundedCopyTexture(RoundedCopyTexture {
|
||||
tex: texture.clone(),
|
||||
source: texcoord,
|
||||
target,
|
||||
alpha,
|
||||
buffer_resv,
|
||||
acquire_sync,
|
||||
release_sync,
|
||||
opaque: false,
|
||||
render_intent,
|
||||
cd: cd.clone(),
|
||||
alpha_mode,
|
||||
size: [width, height],
|
||||
corner_radius: cr,
|
||||
scale,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn sync(&mut self) {
|
||||
self.ops.push(GfxApiOpt::Sync);
|
||||
}
|
||||
|
|
|
|||
34
src/state.rs
34
src/state.rs
|
|
@ -189,14 +189,13 @@ pub struct State {
|
|||
pub theme: Theme,
|
||||
pub pending_container_layout: AsyncQueue<Rc<ContainerNode>>,
|
||||
pub pending_container_render_positions: AsyncQueue<Rc<ContainerNode>>,
|
||||
pub pending_container_render_title: AsyncQueue<Rc<ContainerNode>>,
|
||||
pub pending_output_render_data: AsyncQueue<Rc<OutputNode>>,
|
||||
pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
|
||||
pub pending_float_titles: AsyncQueue<Rc<FloatNode>>,
|
||||
pub pending_input_popup_positioning: AsyncQueue<Rc<ZwpInputPopupSurfaceV2>>,
|
||||
pub pending_toplevel_screencasts: AsyncQueue<Rc<JayScreencast>>,
|
||||
pub pending_screencast_reallocs_or_reconfigures: AsyncQueue<Rc<JayScreencast>>,
|
||||
pub pending_placeholder_render_textures: AsyncQueue<Rc<PlaceholderNode>>,
|
||||
pub pending_container_tab_render_textures: AsyncQueue<Rc<ContainerNode>>,
|
||||
pub dbus: Dbus,
|
||||
pub fdcloser: Arc<FdCloser>,
|
||||
pub logger: Option<Arc<Logger>>,
|
||||
|
|
@ -275,6 +274,7 @@ pub struct State {
|
|||
pub color_manager: Rc<ColorManager>,
|
||||
pub float_above_fullscreen: Cell<bool>,
|
||||
pub icons: Icons,
|
||||
#[allow(dead_code)]
|
||||
pub show_pin_icon: Cell<bool>,
|
||||
pub cl_matcher_manager: ClMatcherManager,
|
||||
pub tl_matcher_manager: TlMatcherManager,
|
||||
|
|
@ -550,10 +550,6 @@ impl DrmDevData {
|
|||
struct UpdateTextTexturesVisitor;
|
||||
impl NodeVisitorBase for UpdateTextTexturesVisitor {
|
||||
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
|
||||
node.children
|
||||
.iter()
|
||||
.for_each(|c| c.title_tex.borrow_mut().clear());
|
||||
node.schedule_render_titles();
|
||||
node.node_visit_children(self);
|
||||
}
|
||||
fn visit_output(&mut self, node: &Rc<OutputNode>) {
|
||||
|
|
@ -561,8 +557,6 @@ impl NodeVisitorBase for UpdateTextTexturesVisitor {
|
|||
node.node_visit_children(self);
|
||||
}
|
||||
fn visit_float(&mut self, node: &Rc<FloatNode>) {
|
||||
node.title_textures.borrow_mut().clear();
|
||||
node.schedule_render_titles();
|
||||
node.node_visit_children(self);
|
||||
}
|
||||
fn visit_workspace(&mut self, node: &Rc<WorkspaceNode>) {
|
||||
|
|
@ -685,10 +679,6 @@ impl State {
|
|||
struct Walker;
|
||||
impl NodeVisitorBase for Walker {
|
||||
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
|
||||
node.render_data.borrow_mut().titles.clear();
|
||||
node.children
|
||||
.iter()
|
||||
.for_each(|c| c.title_tex.borrow_mut().clear());
|
||||
node.node_visit_children(self);
|
||||
}
|
||||
fn visit_workspace(&mut self, node: &Rc<WorkspaceNode>) {
|
||||
|
|
@ -702,7 +692,6 @@ impl State {
|
|||
node.node_visit_children(self);
|
||||
}
|
||||
fn visit_float(&mut self, node: &Rc<FloatNode>) {
|
||||
node.title_textures.borrow_mut().clear();
|
||||
node.node_visit_children(self);
|
||||
}
|
||||
fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>) {
|
||||
|
|
@ -1118,14 +1107,13 @@ impl State {
|
|||
self.dbus.clear();
|
||||
self.pending_container_layout.clear();
|
||||
self.pending_container_render_positions.clear();
|
||||
self.pending_container_render_title.clear();
|
||||
self.pending_output_render_data.clear();
|
||||
self.pending_float_layout.clear();
|
||||
self.pending_float_titles.clear();
|
||||
self.pending_input_popup_positioning.clear();
|
||||
self.pending_toplevel_screencasts.clear();
|
||||
self.pending_screencast_reallocs_or_reconfigures.clear();
|
||||
self.pending_placeholder_render_textures.clear();
|
||||
self.pending_container_tab_render_textures.clear();
|
||||
self.render_ctx_watchers.clear();
|
||||
self.workspace_watchers.clear();
|
||||
self.toplevel_lists.clear();
|
||||
|
|
@ -1296,8 +1284,8 @@ impl State {
|
|||
let (width, height) = target.logical_size(target_transform);
|
||||
Rect::new_sized_saturating(0, 0, width, height)
|
||||
},
|
||||
icons: None,
|
||||
stretch: None,
|
||||
corner_radius: None,
|
||||
};
|
||||
let mut sample_rect = SampleRect::identity();
|
||||
sample_rect.buffer_transform = transform;
|
||||
|
|
@ -1621,8 +1609,7 @@ impl State {
|
|||
.unwrap_or(c);
|
||||
Some(target.predict_child_body_size())
|
||||
} else {
|
||||
let tpuh = self.theme.title_plus_underline_height();
|
||||
Some((pos.width(), (pos.height() - tpuh).max(0)))
|
||||
Some((pos.width(), pos.height()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1863,11 +1850,13 @@ impl State {
|
|||
self.spaces_changed();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_show_titles(&self, show: bool) {
|
||||
self.theme.show_titles.set(show);
|
||||
self.spaces_changed();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_floating_titles(&self, floating: bool) {
|
||||
self.theme.floating_titles.set(floating);
|
||||
self.spaces_changed();
|
||||
|
|
@ -1882,13 +1871,9 @@ impl State {
|
|||
.set(threshold.saturating_mul(threshold));
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_show_pin_icon(&self, show: bool) {
|
||||
self.show_pin_icon.set(show);
|
||||
for stacked in self.root.stacked.iter() {
|
||||
if let Some(float) = stacked.deref().clone().node_into_float() {
|
||||
float.schedule_render_titles();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_float_above_fullscreen(&self, v: bool) {
|
||||
|
|
@ -1909,7 +1894,6 @@ impl State {
|
|||
struct V;
|
||||
impl NodeVisitorBase for V {
|
||||
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
|
||||
node.schedule_render_titles();
|
||||
node.node_visit_children(self);
|
||||
}
|
||||
fn visit_output(&mut self, node: &Rc<OutputNode>) {
|
||||
|
|
@ -1917,7 +1901,6 @@ impl State {
|
|||
node.node_visit_children(self);
|
||||
}
|
||||
fn visit_float(&mut self, node: &Rc<FloatNode>) {
|
||||
node.schedule_render_titles();
|
||||
node.node_visit_children(self);
|
||||
}
|
||||
}
|
||||
|
|
@ -1943,6 +1926,7 @@ impl State {
|
|||
self.fonts_changed();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_title_font(&self, font: Option<&str>) {
|
||||
let font = font.map(|font| Arc::new(font.to_string()));
|
||||
self.theme.title_font.set(font);
|
||||
|
|
|
|||
|
|
@ -415,6 +415,7 @@ impl Shared {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(PartialEq, Default)]
|
||||
enum Config<'a> {
|
||||
#[default]
|
||||
|
|
@ -519,6 +520,7 @@ impl TextTexture {
|
|||
self.data.pending_render.set(Some(pending));
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn schedule_render(
|
||||
&self,
|
||||
on_completed: Rc<dyn OnCompleted>,
|
||||
|
|
|
|||
171
src/theme.rs
171
src/theme.rs
|
|
@ -454,6 +454,14 @@ colors! {
|
|||
bar_text = (0xff, 0xff, 0xff),
|
||||
attention_requested_background = (0x23, 0x09, 0x2c),
|
||||
highlight = (0x9d, 0x28, 0xc6, 0x7f),
|
||||
tab_active_background = (0x4c, 0x78, 0x99),
|
||||
tab_active_border = (0x28, 0x55, 0x77),
|
||||
tab_inactive_background = (0x22, 0x22, 0x22),
|
||||
tab_inactive_border = (0x33, 0x33, 0x33),
|
||||
tab_active_text = (0xff, 0xff, 0xff),
|
||||
tab_inactive_text = (0x88, 0x88, 0x88),
|
||||
tab_bar_background = (0x00, 0x00, 0x00, 0x00),
|
||||
tab_attention_background = (0x23, 0x09, 0x2c),
|
||||
}
|
||||
|
||||
impl StaticText for ThemeColor {
|
||||
|
|
@ -476,6 +484,14 @@ impl StaticText for ThemeColor {
|
|||
ThemeColor::bar_text => "Bar Text",
|
||||
ThemeColor::attention_requested_background => "Attention Requested",
|
||||
ThemeColor::highlight => "Highlight",
|
||||
ThemeColor::tab_active_background => "Tab Background (active)",
|
||||
ThemeColor::tab_active_border => "Tab Border (active)",
|
||||
ThemeColor::tab_inactive_background => "Tab Background (inactive)",
|
||||
ThemeColor::tab_inactive_border => "Tab Border (inactive)",
|
||||
ThemeColor::tab_active_text => "Tab Text (active)",
|
||||
ThemeColor::tab_inactive_text => "Tab Text (inactive)",
|
||||
ThemeColor::tab_bar_background => "Tab Bar Background",
|
||||
ThemeColor::tab_attention_background => "Tab Attention Background",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -588,6 +604,12 @@ sizes! {
|
|||
bar_separator_width = (0, 1000, 1),
|
||||
gap = (0, 1000, 0),
|
||||
title_gap = (0, 1000, 5),
|
||||
tab_bar_height = (0, 1000, 22),
|
||||
tab_bar_padding = (0, 1000, 6),
|
||||
tab_bar_radius = (0, 1000, 6),
|
||||
tab_bar_border_width = (0, 1000, 2),
|
||||
tab_bar_text_padding = (0, 1000, 4),
|
||||
tab_bar_gap = (0, 1000, 4),
|
||||
}
|
||||
|
||||
impl StaticText for ThemeSized {
|
||||
|
|
@ -599,6 +621,12 @@ impl StaticText for ThemeSized {
|
|||
ThemeSized::bar_separator_width => "Bar Separator Width",
|
||||
ThemeSized::gap => "Gap",
|
||||
ThemeSized::title_gap => "Title Gap",
|
||||
ThemeSized::tab_bar_height => "Tab Bar Height",
|
||||
ThemeSized::tab_bar_padding => "Tab Bar Padding",
|
||||
ThemeSized::tab_bar_radius => "Tab Bar Radius",
|
||||
ThemeSized::tab_bar_border_width => "Tab Bar Border Width",
|
||||
ThemeSized::tab_bar_text_padding => "Tab Bar Text Padding",
|
||||
ThemeSized::tab_bar_gap => "Tab Bar Gap",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -643,6 +671,104 @@ impl Into<ConfigBarPosition> for BarPosition {
|
|||
}
|
||||
}
|
||||
|
||||
/// Per-corner radius for rounded rectangles.
|
||||
///
|
||||
/// Each field specifies the radius (in logical pixels) for one corner.
|
||||
/// A radius of 0 means a square corner.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
pub struct CornerRadius {
|
||||
pub top_left: f32,
|
||||
pub top_right: f32,
|
||||
pub bottom_right: f32,
|
||||
pub bottom_left: f32,
|
||||
}
|
||||
|
||||
impl From<f32> for CornerRadius {
|
||||
fn from(value: f32) -> Self {
|
||||
Self {
|
||||
top_left: value,
|
||||
top_right: value,
|
||||
bottom_right: value,
|
||||
bottom_left: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CornerRadius> for [f32; 4] {
|
||||
fn from(cr: CornerRadius) -> Self {
|
||||
[cr.top_left, cr.top_right, cr.bottom_right, cr.bottom_left]
|
||||
}
|
||||
}
|
||||
|
||||
impl CornerRadius {
|
||||
/// Shrink or grow all radii by `width`. Radii that are 0 stay 0 (square
|
||||
/// corners remain square). Negative `width` shrinks; the result is clamped
|
||||
/// to 0.
|
||||
pub fn expanded_by(mut self, width: f32) -> Self {
|
||||
if self.top_left > 0.0 {
|
||||
self.top_left = (self.top_left + width).max(0.0);
|
||||
}
|
||||
if self.top_right > 0.0 {
|
||||
self.top_right = (self.top_right + width).max(0.0);
|
||||
}
|
||||
if self.bottom_right > 0.0 {
|
||||
self.bottom_right = (self.bottom_right + width).max(0.0);
|
||||
}
|
||||
if self.bottom_left > 0.0 {
|
||||
self.bottom_left = (self.bottom_left + width).max(0.0);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Scale all radii by a factor (e.g. for HiDPI).
|
||||
pub fn scaled_by(self, scale: f32) -> Self {
|
||||
Self {
|
||||
top_left: self.top_left * scale,
|
||||
top_right: self.top_right * scale,
|
||||
bottom_right: self.bottom_right * scale,
|
||||
bottom_left: self.bottom_left * scale,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reduce all radii proportionally so that adjacent corners don't overlap,
|
||||
/// following the CSS spec algorithm.
|
||||
pub fn fit_to(self, width: f32, height: f32) -> Self {
|
||||
let reduction = f32::min(
|
||||
f32::min(
|
||||
width / (self.top_left + self.top_right),
|
||||
width / (self.bottom_left + self.bottom_right),
|
||||
),
|
||||
f32::min(
|
||||
height / (self.top_left + self.bottom_left),
|
||||
height / (self.top_right + self.bottom_right),
|
||||
),
|
||||
);
|
||||
let reduction = f32::min(1.0, reduction);
|
||||
Self {
|
||||
top_left: self.top_left * reduction,
|
||||
top_right: self.top_right * reduction,
|
||||
bottom_right: self.bottom_right * reduction,
|
||||
bottom_left: self.bottom_left * reduction,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.top_left == 0.0
|
||||
&& self.top_right == 0.0
|
||||
&& self.bottom_right == 0.0
|
||||
&& self.bottom_left == 0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Horizontal alignment of title text inside tab buttons.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum TabTitleAlign {
|
||||
#[default]
|
||||
Start,
|
||||
Center,
|
||||
End,
|
||||
}
|
||||
|
||||
pub struct Theme {
|
||||
pub colors: ThemeColors,
|
||||
pub sizes: ThemeSizes,
|
||||
|
|
@ -650,9 +776,14 @@ pub struct Theme {
|
|||
pub bar_font: CloneCell<Option<Arc<String>>>,
|
||||
pub title_font: CloneCell<Option<Arc<String>>>,
|
||||
pub default_font: Arc<String>,
|
||||
#[allow(dead_code)]
|
||||
pub show_titles: Cell<bool>,
|
||||
#[allow(dead_code)]
|
||||
pub floating_titles: Cell<bool>,
|
||||
pub bar_position: Cell<BarPosition>,
|
||||
pub corner_radius: Cell<CornerRadius>,
|
||||
pub autotile_enabled: Cell<bool>,
|
||||
pub tab_title_align: Cell<TabTitleAlign>,
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
|
|
@ -668,50 +799,28 @@ impl Default for Theme {
|
|||
show_titles: Cell::new(true),
|
||||
floating_titles: Cell::new(false),
|
||||
bar_position: Default::default(),
|
||||
corner_radius: Cell::new(CornerRadius::default()),
|
||||
autotile_enabled: Cell::new(false),
|
||||
tab_title_align: Cell::new(TabTitleAlign::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn title_font(&self) -> Arc<String> {
|
||||
self.title_font.get().unwrap_or_else(|| self.font.get())
|
||||
}
|
||||
|
||||
pub fn bar_font(&self) -> Arc<String> {
|
||||
self.bar_font.get().unwrap_or_else(|| self.font.get())
|
||||
}
|
||||
|
||||
pub fn title_font(&self) -> Arc<String> {
|
||||
self.title_font.get().unwrap_or_else(|| self.font.get())
|
||||
}
|
||||
|
||||
pub fn title_height(&self) -> i32 {
|
||||
if self.show_titles.get() {
|
||||
self.sizes.title_height.get()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title_underline_height(&self) -> i32 {
|
||||
if self.show_titles.get() { 1 } else { 0 }
|
||||
}
|
||||
|
||||
pub fn floating_title_top_margin(&self) -> i32 {
|
||||
if self.floating_titles.get() && self.sizes.gap.get() != 0 {
|
||||
self.sizes.border_width.get()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
pub fn title_plus_underline_height(&self) -> i32 {
|
||||
if self.show_titles.get() {
|
||||
if self.floating_titles.get() && self.sizes.gap.get() != 0 {
|
||||
let bw = self.sizes.border_width.get();
|
||||
3 * bw + self.sizes.title_height.get() + self.sizes.title_gap.get()
|
||||
} else {
|
||||
self.sizes.title_height.get() + 1
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ mod float;
|
|||
mod output;
|
||||
mod placeholder;
|
||||
mod stacked;
|
||||
pub mod tab_bar;
|
||||
mod toplevel;
|
||||
mod walker;
|
||||
mod workspace;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -10,27 +10,22 @@ use {
|
|||
},
|
||||
rect::Rect,
|
||||
renderer::Renderer,
|
||||
scale::Scale,
|
||||
state::State,
|
||||
text::TextTexture,
|
||||
tree::{
|
||||
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
||||
NodeLayerLink, NodeLocation, OutputNode, PinnedNode, StackedNode, TileDragDestination,
|
||||
ToplevelNode, WorkspaceNode, toplevel_set_floating, walker::NodeVisitor,
|
||||
},
|
||||
utils::{
|
||||
asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState,
|
||||
errorfmt::ErrorFmt, event_listener::LazyEventSource, linkedlist::LinkedNode,
|
||||
on_drop_event::OnDropEvent, smallmap::SmallMapMut,
|
||||
clonecell::CloneCell, double_click_state::DoubleClickState,
|
||||
event_listener::LazyEventSource, linkedlist::LinkedNode,
|
||||
},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
arrayvec::ArrayVec,
|
||||
std::{
|
||||
cell::{Cell, RefCell},
|
||||
fmt::{Debug, Formatter},
|
||||
mem,
|
||||
ops::Deref,
|
||||
rc::Rc,
|
||||
},
|
||||
};
|
||||
|
|
@ -50,10 +45,6 @@ pub struct FloatNode {
|
|||
pub active: Cell<bool>,
|
||||
pub seat_state: NodeSeatState,
|
||||
pub layout_scheduled: Cell<bool>,
|
||||
pub render_titles_scheduled: Cell<bool>,
|
||||
pub title_rect: Cell<Rect>,
|
||||
pub title: RefCell<String>,
|
||||
pub title_textures: RefCell<SmallMapMut<Scale, TextTexture, 2>>,
|
||||
cursors: RefCell<AHashMap<CursorType, CursorState>>,
|
||||
pub attention_requested: Cell<bool>,
|
||||
pub layout_complete: Rc<LazyEventSource>,
|
||||
|
|
@ -99,17 +90,6 @@ pub async fn float_layout(state: Rc<State>) {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn float_titles(state: Rc<State>) {
|
||||
loop {
|
||||
let node = state.pending_float_titles.pop().await;
|
||||
if node.render_titles_scheduled.get() {
|
||||
node.render_titles_scheduled.set(false);
|
||||
node.render_title_phase1().triggered().await;
|
||||
node.render_title_phase2();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatNode {
|
||||
pub fn new(
|
||||
state: &Rc<State>,
|
||||
|
|
@ -131,10 +111,6 @@ impl FloatNode {
|
|||
active: Cell::new(false),
|
||||
seat_state: Default::default(),
|
||||
layout_scheduled: Cell::new(false),
|
||||
render_titles_scheduled: Cell::new(false),
|
||||
title_rect: Default::default(),
|
||||
title: Default::default(),
|
||||
title_textures: Default::default(),
|
||||
cursors: Default::default(),
|
||||
attention_requested: Cell::new(false),
|
||||
layout_complete: state.post_layout_event_sources.create_source(),
|
||||
|
|
@ -162,7 +138,7 @@ impl FloatNode {
|
|||
}
|
||||
|
||||
pub fn on_colors_changed(self: &Rc<Self>) {
|
||||
self.schedule_render_titles();
|
||||
// No title rendering needed
|
||||
}
|
||||
|
||||
pub fn schedule_layout(self: &Rc<Self>) {
|
||||
|
|
@ -179,95 +155,17 @@ impl FloatNode {
|
|||
let pos = self.position.get();
|
||||
let theme = &self.state.theme;
|
||||
let bw = theme.sizes.border_width.get();
|
||||
let th = theme.title_height();
|
||||
let tpuh = theme.title_plus_underline_height();
|
||||
let cpos = Rect::new_sized_saturating(
|
||||
pos.x1() + bw,
|
||||
pos.y1() + bw + tpuh,
|
||||
pos.y1() + bw,
|
||||
pos.width() - 2 * bw,
|
||||
pos.height() - 2 * bw - tpuh,
|
||||
pos.height() - 2 * bw,
|
||||
);
|
||||
let tr = Rect::new_sized_saturating(bw, bw, pos.width() - 2 * bw, th);
|
||||
child.clone().tl_change_extents(&cpos);
|
||||
self.title_rect.set(tr);
|
||||
self.layout_scheduled.set(false);
|
||||
self.schedule_render_titles();
|
||||
self.layout_complete.trigger();
|
||||
}
|
||||
|
||||
pub fn schedule_render_titles(self: &Rc<Self>) {
|
||||
if !self.render_titles_scheduled.replace(true) {
|
||||
self.state.pending_float_titles.push(self.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn render_title_phase1(&self) -> Rc<AsyncEvent> {
|
||||
let on_completed = Rc::new(OnDropEvent::default());
|
||||
let theme = &self.state.theme;
|
||||
let tc = match self.active.get() {
|
||||
true => theme.colors.focused_title_text.get(),
|
||||
false => theme.colors.unfocused_title_text.get(),
|
||||
};
|
||||
let font = theme.title_font();
|
||||
let title = self.title.borrow_mut();
|
||||
let ctx = match self.state.render_ctx.get() {
|
||||
Some(c) => c,
|
||||
_ => return on_completed.event(),
|
||||
};
|
||||
let scales = self.state.scales.lock();
|
||||
let tr = self.title_rect.get();
|
||||
let tt = &mut *self.title_textures.borrow_mut();
|
||||
for (scale, _) in scales.iter() {
|
||||
let tex = tt.get_or_insert_with(*scale, || TextTexture::new(&self.state, &ctx));
|
||||
let mut th = tr.height();
|
||||
let mut scalef = None;
|
||||
let mut width = tr.width();
|
||||
if self.state.show_pin_icon.get() || self.pinned_link.borrow().is_some() {
|
||||
width = (width - th).max(0);
|
||||
}
|
||||
if *scale != 1 {
|
||||
let scale = scale.to_f64();
|
||||
th = (th as f64 * scale).round() as _;
|
||||
width = (width as f64 * scale).round() as _;
|
||||
scalef = Some(scale);
|
||||
}
|
||||
tex.schedule_render(
|
||||
on_completed.clone(),
|
||||
1,
|
||||
None,
|
||||
width,
|
||||
th,
|
||||
1,
|
||||
&font,
|
||||
&title,
|
||||
tc,
|
||||
true,
|
||||
false,
|
||||
scalef,
|
||||
);
|
||||
}
|
||||
on_completed.event()
|
||||
}
|
||||
|
||||
fn render_title_phase2(&self) {
|
||||
let theme = &self.state.theme;
|
||||
let th = theme.title_height();
|
||||
let bw = theme.sizes.border_width.get();
|
||||
let title = self.title.borrow();
|
||||
let tt = &*self.title_textures.borrow();
|
||||
for (_, tt) in tt {
|
||||
if let Err(e) = tt.flip() {
|
||||
log::error!("Could not render title {}: {}", title, ErrorFmt(e));
|
||||
}
|
||||
}
|
||||
let pos = self.position.get();
|
||||
if self.visible.get() && pos.width() >= 2 * bw {
|
||||
let tr =
|
||||
Rect::new_sized_saturating(pos.x1() + bw, pos.y1() + bw, pos.width() - 2 * bw, th);
|
||||
self.state.damage(tr);
|
||||
}
|
||||
}
|
||||
|
||||
fn pointer_move(
|
||||
self: &Rc<Self>,
|
||||
id: CursorType,
|
||||
|
|
@ -280,7 +178,6 @@ impl FloatNode {
|
|||
let y = y.round_down();
|
||||
let theme = &self.state.theme;
|
||||
let bw = theme.sizes.border_width.get();
|
||||
let tpuh = theme.title_plus_underline_height();
|
||||
let mut seats = self.cursors.borrow_mut();
|
||||
let seat_state = seats.entry(id).or_insert_with(|| CursorState {
|
||||
cursor: KnownCursor::Default,
|
||||
|
|
@ -316,7 +213,7 @@ impl FloatNode {
|
|||
}
|
||||
OpType::ResizeTop => {
|
||||
y1 += y - seat_state.dist_ver;
|
||||
y1 = y1.min(y2 - 2 * bw - tpuh);
|
||||
y1 = y1.min(y2 - 2 * bw);
|
||||
}
|
||||
OpType::ResizeRight => {
|
||||
x2 += x - pos.width() + seat_state.dist_hor;
|
||||
|
|
@ -324,31 +221,31 @@ impl FloatNode {
|
|||
}
|
||||
OpType::ResizeBottom => {
|
||||
y2 += y - pos.height() + seat_state.dist_ver;
|
||||
y2 = y2.max(y1 + 2 * bw + tpuh);
|
||||
y2 = y2.max(y1 + 2 * bw);
|
||||
}
|
||||
OpType::ResizeTopLeft => {
|
||||
x1 += x - seat_state.dist_hor;
|
||||
y1 += y - seat_state.dist_ver;
|
||||
x1 = x1.min(x2 - 2 * bw);
|
||||
y1 = y1.min(y2 - 2 * bw - tpuh);
|
||||
y1 = y1.min(y2 - 2 * bw);
|
||||
}
|
||||
OpType::ResizeTopRight => {
|
||||
x2 += x - pos.width() + seat_state.dist_hor;
|
||||
y1 += y - seat_state.dist_ver;
|
||||
x2 = x2.max(x1 + 2 * bw);
|
||||
y1 = y1.min(y2 - 2 * bw - tpuh);
|
||||
y1 = y1.min(y2 - 2 * bw);
|
||||
}
|
||||
OpType::ResizeBottomLeft => {
|
||||
x1 += x - seat_state.dist_hor;
|
||||
y2 += y - pos.height() + seat_state.dist_ver;
|
||||
x1 = x1.min(x2 - 2 * bw);
|
||||
y2 = y2.max(y1 + 2 * bw + tpuh);
|
||||
y2 = y2.max(y1 + 2 * bw);
|
||||
}
|
||||
OpType::ResizeBottomRight => {
|
||||
x2 += x - pos.width() + seat_state.dist_hor;
|
||||
y2 += y - pos.height() + seat_state.dist_ver;
|
||||
x2 = x2.max(x1 + 2 * bw);
|
||||
y2 = y2.max(y1 + 2 * bw + tpuh);
|
||||
y2 = y2.max(y1 + 2 * bw);
|
||||
}
|
||||
}
|
||||
let new_pos = Rect::new_saturating(x1, y1, x2, y2);
|
||||
|
|
@ -474,18 +371,12 @@ impl FloatNode {
|
|||
self.schedule_layout();
|
||||
}
|
||||
|
||||
fn update_child_title(self: &Rc<Self>, title: &str) {
|
||||
let mut t = self.title.borrow_mut();
|
||||
if t.deref() != title {
|
||||
t.clear();
|
||||
t.push_str(title);
|
||||
self.schedule_render_titles();
|
||||
}
|
||||
fn update_child_title(self: &Rc<Self>, _title: &str) {
|
||||
// No title rendering
|
||||
}
|
||||
|
||||
fn update_child_active(self: &Rc<Self>, active: bool) {
|
||||
if self.active.replace(active) != active {
|
||||
self.schedule_render_titles();
|
||||
if active {
|
||||
self.restack();
|
||||
}
|
||||
|
|
@ -542,7 +433,6 @@ impl FloatNode {
|
|||
if let Some(tl) = self.child.get() {
|
||||
tl.tl_data().pinned.set(pl.is_some());
|
||||
}
|
||||
self.schedule_render_titles();
|
||||
}
|
||||
|
||||
fn button(
|
||||
|
|
@ -558,34 +448,6 @@ impl FloatNode {
|
|||
Some(s) => s,
|
||||
_ => return,
|
||||
};
|
||||
let bw = self.state.theme.sizes.border_width.get();
|
||||
let th = self.state.theme.title_height();
|
||||
let mut is_icon_press = false;
|
||||
if pressed && cursor_data.x >= bw && cursor_data.y >= bw && cursor_data.y < bw + th {
|
||||
enum FloatIcon {
|
||||
Pin,
|
||||
}
|
||||
let mut icons = ArrayVec::<FloatIcon, 1>::new();
|
||||
if self.state.show_pin_icon.get() || self.pinned_link.borrow().is_some() {
|
||||
icons.push(FloatIcon::Pin);
|
||||
}
|
||||
let mut x2 = bw + th;
|
||||
let icon = 'icon: {
|
||||
for icon in icons {
|
||||
if cursor_data.x < x2 {
|
||||
break 'icon Some(icon);
|
||||
}
|
||||
x2 += th;
|
||||
}
|
||||
None
|
||||
};
|
||||
if let Some(icon) = icon {
|
||||
is_icon_press = true;
|
||||
match icon {
|
||||
FloatIcon::Pin => self.toggle_pinned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
if !cursor_data.op_active {
|
||||
if !pressed {
|
||||
return;
|
||||
|
|
@ -601,7 +463,6 @@ impl FloatNode {
|
|||
cursor_data.x,
|
||||
cursor_data.y,
|
||||
) && cursor_data.op_type == OpType::Move
|
||||
&& !is_icon_press
|
||||
&& let Some(tl) = self.child.get()
|
||||
{
|
||||
drop(cursors);
|
||||
|
|
@ -653,11 +514,10 @@ impl FloatNode {
|
|||
let child = self.child.get()?;
|
||||
let theme = &self.state.theme.sizes;
|
||||
let bw = theme.border_width.get();
|
||||
let tpuh = self.state.theme.title_plus_underline_height();
|
||||
let pos = self.position.get();
|
||||
let body = Rect::new(
|
||||
pos.x1() + bw,
|
||||
pos.y1() + bw + tpuh,
|
||||
pos.y1() + bw,
|
||||
pos.x2() - bw,
|
||||
pos.y2() - bw,
|
||||
)?;
|
||||
|
|
@ -713,8 +573,8 @@ impl Node for FloatNode {
|
|||
NodeLayerLink::Stacked(l)
|
||||
}
|
||||
|
||||
fn node_child_title_changed(self: Rc<Self>, _child: &dyn Node, title: &str) {
|
||||
self.update_child_title(title);
|
||||
fn node_child_title_changed(self: Rc<Self>, _child: &dyn Node, _title: &str) {
|
||||
// No title rendering
|
||||
}
|
||||
|
||||
fn node_accepts_focus(&self) -> bool {
|
||||
|
|
@ -738,13 +598,12 @@ impl Node for FloatNode {
|
|||
usecase: FindTreeUsecase,
|
||||
) -> FindTreeResult {
|
||||
let theme = &self.state.theme;
|
||||
let tpuh = theme.title_plus_underline_height();
|
||||
let bw = theme.sizes.border_width.get();
|
||||
let pos = self.position.get();
|
||||
if x < bw || x >= pos.width() - bw {
|
||||
return FindTreeResult::AcceptsInput;
|
||||
}
|
||||
if y < bw + tpuh || y >= pos.height() - bw {
|
||||
if y < bw || y >= pos.height() - bw {
|
||||
return FindTreeResult::AcceptsInput;
|
||||
}
|
||||
let child = match self.child.get() {
|
||||
|
|
@ -752,7 +611,7 @@ impl Node for FloatNode {
|
|||
_ => return FindTreeResult::Other,
|
||||
};
|
||||
let x = x - bw;
|
||||
let y = y - bw - tpuh;
|
||||
let y = y - bw;
|
||||
tree.push(FoundNode {
|
||||
node: child.clone(),
|
||||
x,
|
||||
|
|
@ -934,9 +793,8 @@ impl ContainingNode for FloatNode {
|
|||
|
||||
fn cnode_set_child_position(self: Rc<Self>, _child: &dyn Node, x: i32, y: i32) {
|
||||
let theme = &self.state.theme;
|
||||
let tpuh = theme.title_plus_underline_height();
|
||||
let bw = theme.sizes.border_width.get();
|
||||
let (x, y) = (x - bw, y - tpuh - bw);
|
||||
let (x, y) = (x - bw, y - bw);
|
||||
let pos = self.position.get();
|
||||
if pos.position() != (x, y) {
|
||||
let new_pos = pos.at_point(x, y);
|
||||
|
|
@ -956,7 +814,6 @@ impl ContainingNode for FloatNode {
|
|||
new_y2: Option<i32>,
|
||||
) {
|
||||
let theme = &self.state.theme;
|
||||
let tpuh = theme.title_plus_underline_height();
|
||||
let bw = theme.sizes.border_width.get();
|
||||
let pos = self.position.get();
|
||||
let mut x1 = pos.x1();
|
||||
|
|
@ -970,10 +827,10 @@ impl ContainingNode for FloatNode {
|
|||
x2 = (v + bw).max(x1 + bw + bw);
|
||||
}
|
||||
if let Some(v) = new_y1 {
|
||||
y1 = (v - tpuh - bw).min(y2 - bw - tpuh - bw);
|
||||
y1 = (v - bw).min(y2 - bw - bw);
|
||||
}
|
||||
if let Some(v) = new_y2 {
|
||||
y2 = (v + bw).max(y1 + bw + tpuh + bw);
|
||||
y2 = (v + bw).max(y1 + bw + bw);
|
||||
}
|
||||
let new_pos = Rect::new_saturating(x1, y1, x2, y2);
|
||||
if new_pos != pos {
|
||||
|
|
|
|||
113
src/tree/tab_bar.rs
Normal file
113
src/tree/tab_bar.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
use {
|
||||
crate::{
|
||||
scale::Scale,
|
||||
state::State,
|
||||
text::TextTexture,
|
||||
theme::Color,
|
||||
tree::NodeId,
|
||||
},
|
||||
std::{cell::{Cell, RefCell}, rc::Rc},
|
||||
};
|
||||
|
||||
/// A single entry (tab) within a tab bar.
|
||||
pub struct TabBarEntry {
|
||||
/// The node ID of the child this tab represents.
|
||||
pub child_id: NodeId,
|
||||
/// The display title of the tab.
|
||||
pub title: String,
|
||||
/// Pre-rendered text texture for the tab title.
|
||||
pub title_texture: Rc<RefCell<Option<TextTexture>>>,
|
||||
/// Whether this is the active (visible) tab.
|
||||
pub active: bool,
|
||||
/// Whether this tab's window has requested attention.
|
||||
pub attention_requested: bool,
|
||||
/// X offset of this tab within the tab bar (relative to tab bar start).
|
||||
pub x: Cell<i32>,
|
||||
/// Width of this tab in pixels.
|
||||
pub width: Cell<i32>,
|
||||
}
|
||||
|
||||
/// A tab bar rendered above a container in mono (tabbed) mode.
|
||||
pub struct TabBar {
|
||||
/// The individual tab entries.
|
||||
pub entries: Vec<TabBarEntry>,
|
||||
/// Height of the tab bar in pixels (from theme).
|
||||
pub height: i32,
|
||||
/// The output scale at which text textures were rendered.
|
||||
pub render_scale: Scale,
|
||||
}
|
||||
|
||||
impl TabBar {
|
||||
/// Create a new empty tab bar.
|
||||
pub fn new(height: i32, render_scale: Scale) -> Self {
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
height,
|
||||
render_scale,
|
||||
}
|
||||
}
|
||||
|
||||
/// Recompute the positions and widths of all tab entries.
|
||||
///
|
||||
/// `total_width` is the available width for the entire tab bar.
|
||||
pub fn layout_entries(&self, total_width: i32, padding: i32) {
|
||||
let n = self.entries.len() as i32;
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
let total_padding = padding * (n + 1);
|
||||
let available = (total_width - total_padding).max(0);
|
||||
let per_tab = available / n;
|
||||
let mut remainder = available - per_tab * n;
|
||||
let mut x = padding;
|
||||
for entry in &self.entries {
|
||||
let w = if remainder > 0 {
|
||||
remainder -= 1;
|
||||
per_tab + 1
|
||||
} else {
|
||||
per_tab
|
||||
};
|
||||
entry.x.set(x);
|
||||
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);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the colors for a tab entry based on its state.
|
||||
pub fn entry_colors(state: &State, entry: &TabBarEntry) -> (Color, Color, Color) {
|
||||
let theme = &state.theme;
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ use {
|
|||
},
|
||||
ahash::AHashMap,
|
||||
jay_config::{
|
||||
Axis, Direction, Workspace,
|
||||
Direction, Workspace,
|
||||
client::ClientCapabilities,
|
||||
input::{
|
||||
FallbackOutputMode, LayerDirection, SwitchEvent, Timeline, acceleration::AccelProfile,
|
||||
|
|
@ -60,15 +60,10 @@ pub enum SimpleCommand {
|
|||
Quit,
|
||||
ReloadConfigSo,
|
||||
ReloadConfigToml,
|
||||
Split(Axis),
|
||||
ToggleFloating,
|
||||
SetFloating(bool),
|
||||
ToggleFullscreen,
|
||||
SetFullscreen(bool),
|
||||
ToggleMono,
|
||||
SetMono(bool),
|
||||
ToggleSplit,
|
||||
SetSplit(Axis),
|
||||
Forward(bool),
|
||||
EnableWindowManagement(bool),
|
||||
SetFloatAboveFullscreen(bool),
|
||||
|
|
@ -94,6 +89,17 @@ pub enum SimpleCommand {
|
|||
ReloadSimpleIm,
|
||||
EnableUnicodeInput,
|
||||
WarpMouseToFocus,
|
||||
ToggleTab,
|
||||
MakeGroupH,
|
||||
MakeGroupV,
|
||||
MakeGroupTab,
|
||||
ChangeGroupOpposite,
|
||||
Equalize,
|
||||
EqualizeRecursive,
|
||||
MoveTabLeft,
|
||||
MoveTabRight,
|
||||
SetAutotile(bool),
|
||||
ToggleAutotile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -228,6 +234,22 @@ pub struct Theme {
|
|||
pub gap: Option<i32>,
|
||||
pub floating_titles: Option<bool>,
|
||||
pub title_gap: Option<i32>,
|
||||
pub corner_radius: Option<f32>,
|
||||
pub tab_active_bg_color: Option<Color>,
|
||||
pub tab_active_border_color: Option<Color>,
|
||||
pub tab_inactive_bg_color: Option<Color>,
|
||||
pub tab_inactive_border_color: Option<Color>,
|
||||
pub tab_active_text_color: Option<Color>,
|
||||
pub tab_inactive_text_color: Option<Color>,
|
||||
pub tab_bar_bg_color: Option<Color>,
|
||||
pub tab_attention_bg_color: Option<Color>,
|
||||
pub tab_bar_height: Option<i32>,
|
||||
pub tab_bar_padding: Option<i32>,
|
||||
pub tab_bar_radius: Option<i32>,
|
||||
pub tab_bar_border_width: Option<i32>,
|
||||
pub tab_bar_text_padding: Option<i32>,
|
||||
pub tab_bar_gap: Option<i32>,
|
||||
pub tab_title_align: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ use {
|
|||
},
|
||||
indexmap::IndexMap,
|
||||
jay_config::{
|
||||
Axis::{Horizontal, Vertical},
|
||||
Direction, get_workspace,
|
||||
input::{LayerDirection, Timeline},
|
||||
},
|
||||
|
|
@ -115,14 +114,6 @@ impl ActionParser<'_> {
|
|||
"move-down" => Move(Down),
|
||||
"move-up" => Move(Up),
|
||||
"move-right" => Move(Right),
|
||||
"split-horizontal" => Split(Horizontal),
|
||||
"split-vertical" => Split(Vertical),
|
||||
"toggle-split" => ToggleSplit,
|
||||
"tile-horizontal" => SetSplit(Horizontal),
|
||||
"tile-vertical" => SetSplit(Vertical),
|
||||
"toggle-mono" => ToggleMono,
|
||||
"show-single" => SetMono(true),
|
||||
"show-all" => SetMono(false),
|
||||
"toggle-fullscreen" => ToggleFullscreen,
|
||||
"enter-fullscreen" => SetFullscreen(true),
|
||||
"exit-fullscreen" => SetFullscreen(false),
|
||||
|
|
@ -172,6 +163,18 @@ impl ActionParser<'_> {
|
|||
"reload-simple-im" => ReloadSimpleIm,
|
||||
"enable-unicode-input" => EnableUnicodeInput,
|
||||
"warp-mouse-to-focus" => WarpMouseToFocus,
|
||||
"toggle-tab" => ToggleTab,
|
||||
"make-group-h" => MakeGroupH,
|
||||
"make-group-v" => MakeGroupV,
|
||||
"make-group-tab" => MakeGroupTab,
|
||||
"change-group-opposite" => ChangeGroupOpposite,
|
||||
"equalize" => Equalize,
|
||||
"equalize-recursive" => EqualizeRecursive,
|
||||
"move-tab-left" => MoveTabLeft,
|
||||
"move-tab-right" => MoveTabRight,
|
||||
"enable-autotile" => SetAutotile(true),
|
||||
"disable-autotile" => SetAutotile(false),
|
||||
"toggle-autotile" => ToggleAutotile,
|
||||
_ => {
|
||||
return Err(
|
||||
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use {
|
|||
config::{
|
||||
Theme,
|
||||
context::Context,
|
||||
extractor::{Extractor, ExtractorError, bol, opt, recover, s32, str, val},
|
||||
extractor::{Extractor, ExtractorError, bol, fltorint, opt, recover, s32, str, val},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::color::ColorParser,
|
||||
},
|
||||
|
|
@ -97,7 +97,45 @@ impl Parser for ThemeParser<'_> {
|
|||
recover(opt(bol("floating-titles"))),
|
||||
),
|
||||
))?;
|
||||
let (title_gap,) = ext.extract((recover(opt(s32("title-gap"))),))?;
|
||||
let (title_gap, corner_radius) = ext.extract((
|
||||
recover(opt(s32("title-gap"))),
|
||||
recover(opt(fltorint("corner-radius"))),
|
||||
))?;
|
||||
let (
|
||||
(
|
||||
tab_active_bg_color,
|
||||
tab_active_border_color,
|
||||
tab_inactive_bg_color,
|
||||
tab_inactive_border_color,
|
||||
tab_active_text_color,
|
||||
tab_inactive_text_color,
|
||||
tab_bar_bg_color,
|
||||
tab_attention_bg_color,
|
||||
tab_bar_height,
|
||||
tab_bar_padding,
|
||||
),
|
||||
(tab_bar_radius, tab_bar_border_width, tab_bar_text_padding, tab_bar_gap, tab_title_align_val),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(val("tab-active-bg-color")),
|
||||
opt(val("tab-active-border-color")),
|
||||
opt(val("tab-inactive-bg-color")),
|
||||
opt(val("tab-inactive-border-color")),
|
||||
opt(val("tab-active-text-color")),
|
||||
opt(val("tab-inactive-text-color")),
|
||||
opt(val("tab-bar-bg-color")),
|
||||
opt(val("tab-attention-bg-color")),
|
||||
recover(opt(s32("tab-bar-height"))),
|
||||
recover(opt(s32("tab-bar-padding"))),
|
||||
),
|
||||
(
|
||||
recover(opt(s32("tab-bar-radius"))),
|
||||
recover(opt(s32("tab-bar-border-width"))),
|
||||
recover(opt(s32("tab-bar-text-padding"))),
|
||||
recover(opt(s32("tab-bar-gap"))),
|
||||
recover(opt(str("tab-title-align"))),
|
||||
),
|
||||
))?;
|
||||
macro_rules! color {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
|
|
@ -152,6 +190,22 @@ impl Parser for ThemeParser<'_> {
|
|||
gap: gap.despan(),
|
||||
floating_titles: floating_titles.despan(),
|
||||
title_gap: title_gap.despan(),
|
||||
corner_radius: corner_radius.map(|v| v.value as f32),
|
||||
tab_active_bg_color: color!(tab_active_bg_color),
|
||||
tab_active_border_color: color!(tab_active_border_color),
|
||||
tab_inactive_bg_color: color!(tab_inactive_bg_color),
|
||||
tab_inactive_border_color: color!(tab_inactive_border_color),
|
||||
tab_active_text_color: color!(tab_active_text_color),
|
||||
tab_inactive_text_color: color!(tab_inactive_text_color),
|
||||
tab_bar_bg_color: color!(tab_bar_bg_color),
|
||||
tab_attention_bg_color: color!(tab_attention_bg_color),
|
||||
tab_bar_height: tab_bar_height.despan(),
|
||||
tab_bar_padding: tab_bar_padding.despan(),
|
||||
tab_bar_radius: tab_bar_radius.despan(),
|
||||
tab_bar_border_width: tab_bar_border_width.despan(),
|
||||
tab_bar_text_padding: tab_bar_text_padding.despan(),
|
||||
tab_bar_gap: tab_bar_gap.despan(),
|
||||
tab_title_align: tab_title_align_val.map(|v| v.value.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ use {
|
|||
ahash::{AHashMap, AHashSet},
|
||||
error_reporter::Report,
|
||||
jay_config::{
|
||||
Axis,
|
||||
client::Client,
|
||||
config, config_dir,
|
||||
exec::{Command, set_env, unset_env},
|
||||
|
|
@ -40,7 +41,7 @@ use {
|
|||
set_color_management_enabled, set_default_workspace_capture, set_explicit_sync_enabled,
|
||||
set_float_above_fullscreen, set_idle, set_idle_grace_period,
|
||||
set_floating_titles, set_middle_click_paste_enabled, set_show_bar, set_show_float_pin_icon,
|
||||
set_show_titles,
|
||||
set_show_titles, set_corner_radius, set_autotile, set_tab_title_align,
|
||||
set_ui_drag_enabled, set_ui_drag_threshold,
|
||||
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
||||
switch_to_vt,
|
||||
|
|
@ -169,11 +170,6 @@ impl Action {
|
|||
Action::SimpleCommand { cmd } => match cmd {
|
||||
SimpleCommand::Focus(dir) => b.new(move || s.focus(dir)),
|
||||
SimpleCommand::Move(dir) => window_or_seat!(s, s.move_(dir)),
|
||||
SimpleCommand::Split(axis) => window_or_seat!(s, s.create_split(axis)),
|
||||
SimpleCommand::ToggleSplit => window_or_seat!(s, s.toggle_split()),
|
||||
SimpleCommand::SetSplit(b) => window_or_seat!(s, s.set_split(b)),
|
||||
SimpleCommand::ToggleMono => window_or_seat!(s, s.toggle_mono()),
|
||||
SimpleCommand::SetMono(b) => window_or_seat!(s, s.set_mono(b)),
|
||||
SimpleCommand::ToggleFullscreen => window_or_seat!(s, s.toggle_fullscreen()),
|
||||
SimpleCommand::SetFullscreen(b) => window_or_seat!(s, s.set_fullscreen(b)),
|
||||
SimpleCommand::FocusParent => b.new(move || s.focus_parent()),
|
||||
|
|
@ -259,6 +255,35 @@ impl Action {
|
|||
let persistent = state.persistent.clone();
|
||||
b.new(move || persistent.seat.warp_mouse_to_focus())
|
||||
}
|
||||
SimpleCommand::ToggleTab => b.new(move || s.toggle_tab()),
|
||||
SimpleCommand::MakeGroupH => {
|
||||
b.new(move || s.make_group(Axis::Horizontal, true))
|
||||
}
|
||||
SimpleCommand::MakeGroupV => {
|
||||
b.new(move || s.make_group(Axis::Vertical, true))
|
||||
}
|
||||
SimpleCommand::MakeGroupTab => {
|
||||
b.new(move || {
|
||||
s.make_group(Axis::Horizontal, true);
|
||||
s.toggle_tab();
|
||||
})
|
||||
}
|
||||
SimpleCommand::ChangeGroupOpposite => {
|
||||
b.new(move || s.change_group_opposite())
|
||||
}
|
||||
SimpleCommand::Equalize => b.new(move || s.equalize(false)),
|
||||
SimpleCommand::EqualizeRecursive => b.new(move || s.equalize(true)),
|
||||
SimpleCommand::MoveTabLeft => b.new(move || s.move_tab(false)),
|
||||
SimpleCommand::MoveTabRight => b.new(move || s.move_tab(true)),
|
||||
SimpleCommand::SetAutotile(enabled) => {
|
||||
b.new(move || set_autotile(enabled))
|
||||
}
|
||||
SimpleCommand::ToggleAutotile => {
|
||||
b.new(move || {
|
||||
// Toggle not directly supported; set to true
|
||||
set_autotile(true)
|
||||
})
|
||||
}
|
||||
},
|
||||
Action::Multi { actions } => {
|
||||
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
||||
|
|
@ -997,6 +1022,14 @@ impl State {
|
|||
color!(UNFOCUSED_TITLE_BACKGROUND_COLOR, unfocused_title_bg_color);
|
||||
color!(UNFOCUSED_TITLE_TEXT_COLOR, unfocused_title_text_color);
|
||||
color!(HIGHLIGHT_COLOR, highlight_color);
|
||||
color!(TAB_ACTIVE_BACKGROUND_COLOR, tab_active_bg_color);
|
||||
color!(TAB_ACTIVE_BORDER_COLOR, tab_active_border_color);
|
||||
color!(TAB_INACTIVE_BACKGROUND_COLOR, tab_inactive_bg_color);
|
||||
color!(TAB_INACTIVE_BORDER_COLOR, tab_inactive_border_color);
|
||||
color!(TAB_ACTIVE_TEXT_COLOR, tab_active_text_color);
|
||||
color!(TAB_INACTIVE_TEXT_COLOR, tab_inactive_text_color);
|
||||
color!(TAB_BAR_BACKGROUND_COLOR, tab_bar_bg_color);
|
||||
color!(TAB_ATTENTION_BACKGROUND_COLOR, tab_attention_bg_color);
|
||||
macro_rules! size {
|
||||
($sized:ident, $field:ident) => {
|
||||
if let Some(size) = theme.$field {
|
||||
|
|
@ -1010,6 +1043,12 @@ impl State {
|
|||
size!(BAR_SEPARATOR_WIDTH, bar_separator_width);
|
||||
size!(GAP, gap);
|
||||
size!(TITLE_GAP, title_gap);
|
||||
size!(TAB_BAR_HEIGHT, tab_bar_height);
|
||||
size!(TAB_BAR_PADDING, tab_bar_padding);
|
||||
size!(TAB_BAR_RADIUS, tab_bar_radius);
|
||||
size!(TAB_BAR_BORDER_WIDTH, tab_bar_border_width);
|
||||
size!(TAB_BAR_TEXT_PADDING, tab_bar_text_padding);
|
||||
size!(TAB_BAR_GAP, tab_bar_gap);
|
||||
macro_rules! font {
|
||||
($fun:ident, $field:ident) => {
|
||||
if let Some(font) = &theme.$field {
|
||||
|
|
@ -1020,6 +1059,12 @@ impl State {
|
|||
font!(set_font, font);
|
||||
font!(set_title_font, title_font);
|
||||
font!(set_bar_font, bar_font);
|
||||
if let Some(radius) = theme.corner_radius {
|
||||
set_corner_radius(radius);
|
||||
}
|
||||
if let Some(ref align) = theme.tab_title_align {
|
||||
set_tab_title_align(align);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_switch_device(self: &Rc<Self>, dev: InputDevice, actions: &Rc<SwitchActions>) {
|
||||
|
|
|
|||
|
|
@ -2426,6 +2426,11 @@ Theme:
|
|||
kind: string
|
||||
required: false
|
||||
description: The name of the font to use in the bar. Defaults to `font` if not set.
|
||||
corner-radius:
|
||||
kind: number
|
||||
minimum: 0
|
||||
required: false
|
||||
description: The corner radius for window borders in logical pixels. Defaults to 0 (square corners).
|
||||
|
||||
|
||||
Config:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue