Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
| 3540cdc4be |
69 changed files with 463 additions and 7729 deletions
|
|
@ -77,20 +77,6 @@ You can also right-click any title in a container to toggle mono mode.
|
||||||
In mono mode, scroll over the title bar to cycle between windows in the
|
In mono mode, scroll over the title bar to cycle between windows in the
|
||||||
container.
|
container.
|
||||||
|
|
||||||
## Autotiling
|
|
||||||
|
|
||||||
Autotiling makes newly tiled windows alternate split direction from the focused
|
|
||||||
tiled window. The first split uses the containing group direction, then later
|
|
||||||
windows wrap the focused tile in the opposite direction, producing a horizontal,
|
|
||||||
vertical, horizontal pattern as the layout grows.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[shortcuts]
|
|
||||||
alt-a = "toggle-autotile"
|
|
||||||
```
|
|
||||||
|
|
||||||
Manual grouping and split commands still use the direction you request.
|
|
||||||
|
|
||||||
## Fullscreen
|
## Fullscreen
|
||||||
|
|
||||||
Press `alt-u` (`toggle-fullscreen`) to make the focused window fill the entire
|
Press `alt-u` (`toggle-fullscreen`) to make the focused window fill the entire
|
||||||
|
|
|
||||||
200
docs/window-animations-plan.md
Normal file
200
docs/window-animations-plan.md
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
# Window Animations Plan
|
||||||
|
|
||||||
|
This document is the working plan for Wry's window animation system. It records
|
||||||
|
the decisions already made, the implementation phases, and the risks that must
|
||||||
|
be handled deliberately.
|
||||||
|
|
||||||
|
## Accepted Decisions
|
||||||
|
|
||||||
|
- The first landed slice is linear interpolation only, disabled by default.
|
||||||
|
- Animation is presentation-only. Logical layout, input hit testing, focus, and
|
||||||
|
Wayland configure state use final geometry immediately.
|
||||||
|
- Pointer drag and resize initiated by the mouse or tablet do not animate.
|
||||||
|
- Linear animations restart only for windows whose destination changes. Other
|
||||||
|
in-flight windows keep their existing timelines.
|
||||||
|
- Spawn-in uses scale and position. Spawn-out requires retained visual content
|
||||||
|
and is deferred until the freezing layer exists.
|
||||||
|
- Command-driven tile-to-float and float-to-tile transitions may animate.
|
||||||
|
Protocol drag/drop paths do not.
|
||||||
|
- The no-overlap multiphase system is a separate phase after the linear path is
|
||||||
|
working and testable.
|
||||||
|
- Content freezing will use retained per-surface texture references, not a full
|
||||||
|
offscreen snapshot as the default design.
|
||||||
|
- The multiphase animation concept is original to Wry. Hy3 is relevant only as
|
||||||
|
partial inspiration for tiling style and titlebar/grouping behavior.
|
||||||
|
- Mono mode should mostly avoid animations. Exceptions are windows entering or
|
||||||
|
exiting mono mode, where a visual transition can clarify the hierarchy change.
|
||||||
|
|
||||||
|
## Texture Freezing Decision
|
||||||
|
|
||||||
|
The approved freezing design is to capture a renderable surface tree at animation
|
||||||
|
start:
|
||||||
|
|
||||||
|
- texture references
|
||||||
|
- source sample rects
|
||||||
|
- target sizes
|
||||||
|
- alpha and color metadata
|
||||||
|
- subsurface offsets and stacking order
|
||||||
|
- enough synchronization/release state to keep referenced buffers alive safely
|
||||||
|
|
||||||
|
This is lighter than rendering every toplevel into a compositor-owned offscreen
|
||||||
|
texture, and it should handle normal GPU-backed windows without an extra full
|
||||||
|
window copy. It also gives spawn-out a path: capture the surface tree before the
|
||||||
|
toplevel is logically destroyed, then animate the retained records after the live
|
||||||
|
node is gone.
|
||||||
|
|
||||||
|
Tradeoffs:
|
||||||
|
|
||||||
|
- Retained references can delay buffer release. For dmabuf clients this can
|
||||||
|
increase memory pressure or throttle clients if many large windows animate.
|
||||||
|
- SHM buffers still matter for simple clients, fallback paths, some utilities,
|
||||||
|
and cursor-like surfaces. They are probably not the common case for large app
|
||||||
|
windows, but the implementation must still treat SHM texture flipping as a
|
||||||
|
correctness issue.
|
||||||
|
- The release/sync contract must be explicit. A retained texture must not be
|
||||||
|
released back to the client while the compositor may still render it.
|
||||||
|
- True offscreen snapshots remain a possible fallback for cases where retained
|
||||||
|
references cannot safely preserve the rendered content.
|
||||||
|
|
||||||
|
## Phase 1: Linear Presentation Animations
|
||||||
|
|
||||||
|
Goal: add the smallest correct animation layer without changing layout semantics.
|
||||||
|
|
||||||
|
Implementation shape:
|
||||||
|
|
||||||
|
- Add animation state owned by `State`.
|
||||||
|
- Track per-toplevel animation entries keyed by `NodeId`.
|
||||||
|
- Store logical target rect, current presentation rect, previous damaged rect,
|
||||||
|
start time, duration, and curve.
|
||||||
|
- On command-driven tiled layout geometry changes, animate from current
|
||||||
|
presentation rect to new final rect.
|
||||||
|
- On interruption, restart only the affected window from its current
|
||||||
|
presentation rect.
|
||||||
|
- Drive frames from the existing output latch/presentation event flow.
|
||||||
|
- Damage the union of previous presentation rect, current presentation rect, and
|
||||||
|
final logical rect.
|
||||||
|
|
||||||
|
Initial scope:
|
||||||
|
|
||||||
|
- Tiled reflow animation.
|
||||||
|
- Floating command-driven moves, tile-to-float, float-to-tile, and spawn-in are
|
||||||
|
deferred until after tiled reflow is validated.
|
||||||
|
- Cross-output and cross-scale movements snap for now.
|
||||||
|
- Linear mode may overlap windows during swaps. That is expected for the classic
|
||||||
|
interpolation mode; no-overlap is Phase 3.
|
||||||
|
- Live client buffers are rendered in Phase 1. Retained content freezing is
|
||||||
|
deferred, but animated windows must still be clipped to their presentation
|
||||||
|
bounds and must preserve the existing stretch behavior for undersized contents.
|
||||||
|
- No spawn-out.
|
||||||
|
- No content freezing.
|
||||||
|
- No multiphase no-overlap planner.
|
||||||
|
|
||||||
|
Tests:
|
||||||
|
|
||||||
|
- rect interpolation is direction-independent
|
||||||
|
- interruption restarts only changed windows
|
||||||
|
- unchanged in-flight windows keep their original timeline
|
||||||
|
- drag-driven floating movement bypasses animation
|
||||||
|
- damage includes old, current, and final rects
|
||||||
|
|
||||||
|
## Phase 2: Retained Texture Freezing
|
||||||
|
|
||||||
|
Goal: freeze visual contents during movement and enable spawn-out.
|
||||||
|
|
||||||
|
Implementation shape:
|
||||||
|
|
||||||
|
- Add a retained render-record tree for toplevel surfaces.
|
||||||
|
- Capture records before movement animations that require freezing.
|
||||||
|
- Capture records before destroy/unmap for spawn-out.
|
||||||
|
- Render retained records through the normal renderer primitives where possible.
|
||||||
|
- Extend event/sync handling so retained buffers remain valid until the animation
|
||||||
|
is complete.
|
||||||
|
|
||||||
|
Open design work:
|
||||||
|
|
||||||
|
- Exact lifetime model for retained `SurfaceBuffer` / `GfxTexture` references.
|
||||||
|
- Whether retained records participate in frame callbacks or presentation
|
||||||
|
feedback. Default assumption: no, because they are compositor animation frames,
|
||||||
|
not client commits.
|
||||||
|
- How to fall back when a buffer cannot be safely retained.
|
||||||
|
|
||||||
|
## Phase 3: Multiphase No-Overlap Animations
|
||||||
|
|
||||||
|
Goal: implement Wry's staged no-overlap planner while preserving the rule that
|
||||||
|
windows never overlap.
|
||||||
|
|
||||||
|
Core rules:
|
||||||
|
|
||||||
|
- Each phase is a discrete animation using the full curve.
|
||||||
|
- A phase performs only one action kind per window: move or scale.
|
||||||
|
- Movement and scaling are split by axis.
|
||||||
|
- No diagonal motion.
|
||||||
|
- A window or synchronized group owns its own timeline.
|
||||||
|
- New layout changes interrupt only windows/groups with changed destinations.
|
||||||
|
- Current hierarchy and target hierarchy both matter. The planner must know
|
||||||
|
whether a window is ascending toward a higher-level/toplevel position,
|
||||||
|
descending into a container, or moving between containers at the same depth.
|
||||||
|
- If some child windows require fewer phases than their parent/container
|
||||||
|
context, parent/container-space changes generally happen first so space exists
|
||||||
|
before the child moves into it. This rule can be overridden only when the
|
||||||
|
non-overlap invariant still clearly holds.
|
||||||
|
- Windows that become peers in the target hierarchy may synchronize later
|
||||||
|
phases even if they were not peers in the source hierarchy.
|
||||||
|
|
||||||
|
Important parent/child synchronization issue:
|
||||||
|
|
||||||
|
The planner must not let a parent container and child window animate independent
|
||||||
|
axes at the same time in a way that violates the visual rules. For example, a
|
||||||
|
parent scaling horizontally while a child scales vertically can accidentally
|
||||||
|
produce a diagonal or multi-axis motion in screen space.
|
||||||
|
|
||||||
|
Preferred approach:
|
||||||
|
|
||||||
|
- Plan in terms of leaf toplevel visual rectangles first.
|
||||||
|
- Treat containers as constraints and grouping boundaries, not as independently
|
||||||
|
animated visual actors.
|
||||||
|
- Derive every leaf's per-phase rect from one phase schedule so parent and child
|
||||||
|
effects cannot compose into forbidden motion.
|
||||||
|
- Add container-level grouping only after the leaf planner proves correct.
|
||||||
|
- Include hierarchy-transition metadata in the planner input: source parent,
|
||||||
|
target parent, source depth, target depth, and whether the window is ascending,
|
||||||
|
descending, or staying at the same hierarchy level.
|
||||||
|
- For mono containers, suppress ordinary in-mono focus/tab changes. Animate only
|
||||||
|
transitions into mono, out of mono, or across the mono boundary.
|
||||||
|
|
||||||
|
Tests:
|
||||||
|
|
||||||
|
- horizontal swaps shrink, move, then grow without overlap
|
||||||
|
- extraction from a stack creates space before moving the extracted window
|
||||||
|
- nested containers do not produce simultaneous cross-axis motion
|
||||||
|
- interruption restarts only affected phase groups
|
||||||
|
- reversing direction produces equivalent motion in reverse
|
||||||
|
- child waits for parent/container-space phases when moving upward into a
|
||||||
|
toplevel peer position
|
||||||
|
- mono-mode tab switches do not animate, while entering/exiting mono can animate
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Phase 1 should expose a disabled-by-default setting for:
|
||||||
|
|
||||||
|
- enabled/disabled
|
||||||
|
- duration
|
||||||
|
- curve preset or cubic bezier
|
||||||
|
|
||||||
|
Initial TOML shape:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[animations]
|
||||||
|
enabled = false
|
||||||
|
duration-ms = 160
|
||||||
|
curve = "ease-out"
|
||||||
|
```
|
||||||
|
|
||||||
|
Bezier curves should be analyzed at configuration time and stored in a form that
|
||||||
|
is cheap to evaluate during rendering.
|
||||||
|
|
||||||
|
## Existing Note
|
||||||
|
|
||||||
|
`docs/animation-integration.md` appears to document a prior animation attempt
|
||||||
|
whose `src/animation/` implementation is not present in this checkout. Treat
|
||||||
|
this plan as the current source of truth until implementation docs are updated.
|
||||||
|
|
@ -640,22 +640,6 @@ impl ConfigClient {
|
||||||
self.send(&ClientMessage::SetWindowWorkspace { window, workspace });
|
self.send(&ClientMessage::SetWindowWorkspace { window, workspace });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn seat_send_to_scratchpad(&self, seat: Seat, name: &str) {
|
|
||||||
self.send(&ClientMessage::SeatSendToScratchpad { seat, name });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn seat_toggle_scratchpad(&self, seat: Seat, name: &str) {
|
|
||||||
self.send(&ClientMessage::SeatToggleScratchpad { seat, name });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn seat_cycle_scratchpad(&self, seat: Seat, name: &str) {
|
|
||||||
self.send(&ClientMessage::SeatCycleScratchpad { seat, name });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_send_to_scratchpad(&self, window: Window, name: &str) {
|
|
||||||
self.send(&ClientMessage::WindowSendToScratchpad { window, name });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn seat_split(&self, seat: Seat) -> Axis {
|
pub fn seat_split(&self, seat: Seat) -> Axis {
|
||||||
let res = self.send_with_response(&ClientMessage::GetSeatSplit { seat });
|
let res = self.send_with_response(&ClientMessage::GetSeatSplit { seat });
|
||||||
get_response!(res, Axis::Horizontal, GetSplit { axis });
|
get_response!(res, Axis::Horizontal, GetSplit { axis });
|
||||||
|
|
@ -1051,14 +1035,6 @@ impl ConfigClient {
|
||||||
self.send(&ClientMessage::SetAnimationCurve { curve });
|
self.send(&ClientMessage::SetAnimationCurve { curve });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_animation_style(&self, style: u32) {
|
|
||||||
self.send(&ClientMessage::SetAnimationStyle { style });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) {
|
|
||||||
self.send(&ClientMessage::SetAnimationCubicBezier { x1, y1, x2, y2 });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_color_management_enabled(&self, enabled: bool) {
|
pub fn set_color_management_enabled(&self, enabled: bool) {
|
||||||
self.send(&ClientMessage::SetColorManagementEnabled { enabled });
|
self.send(&ClientMessage::SetColorManagementEnabled { enabled });
|
||||||
}
|
}
|
||||||
|
|
@ -2095,12 +2071,6 @@ impl ConfigClient {
|
||||||
self.send(&ClientMessage::SetAutotile { enabled });
|
self.send(&ClientMessage::SetAutotile { enabled });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_autotile(&self) -> bool {
|
|
||||||
let res = self.send_with_response(&ClientMessage::GetAutotile);
|
|
||||||
get_response!(res, false, GetAutotile { enabled });
|
|
||||||
enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_tab_title_align(&self, align: u32) {
|
pub fn set_tab_title_align(&self, align: u32) {
|
||||||
self.send(&ClientMessage::SetTabTitleAlign { align });
|
self.send(&ClientMessage::SetTabTitleAlign { align });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -286,18 +286,6 @@ pub enum ClientMessage<'a> {
|
||||||
seat: Seat,
|
seat: Seat,
|
||||||
workspace: Workspace,
|
workspace: Workspace,
|
||||||
},
|
},
|
||||||
SeatSendToScratchpad {
|
|
||||||
seat: Seat,
|
|
||||||
name: &'a str,
|
|
||||||
},
|
|
||||||
SeatToggleScratchpad {
|
|
||||||
seat: Seat,
|
|
||||||
name: &'a str,
|
|
||||||
},
|
|
||||||
SeatCycleScratchpad {
|
|
||||||
seat: Seat,
|
|
||||||
name: &'a str,
|
|
||||||
},
|
|
||||||
GetTimer {
|
GetTimer {
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
},
|
},
|
||||||
|
|
@ -566,15 +554,6 @@ pub enum ClientMessage<'a> {
|
||||||
SetAnimationCurve {
|
SetAnimationCurve {
|
||||||
curve: u32,
|
curve: u32,
|
||||||
},
|
},
|
||||||
SetAnimationStyle {
|
|
||||||
style: u32,
|
|
||||||
},
|
|
||||||
SetAnimationCubicBezier {
|
|
||||||
x1: f32,
|
|
||||||
y1: f32,
|
|
||||||
x2: f32,
|
|
||||||
y2: f32,
|
|
||||||
},
|
|
||||||
SetXScalingMode {
|
SetXScalingMode {
|
||||||
mode: XScalingMode,
|
mode: XScalingMode,
|
||||||
},
|
},
|
||||||
|
|
@ -699,10 +678,6 @@ pub enum ClientMessage<'a> {
|
||||||
window: Window,
|
window: Window,
|
||||||
workspace: Workspace,
|
workspace: Workspace,
|
||||||
},
|
},
|
||||||
WindowSendToScratchpad {
|
|
||||||
window: Window,
|
|
||||||
name: &'a str,
|
|
||||||
},
|
|
||||||
SetWindowFullscreen {
|
SetWindowFullscreen {
|
||||||
window: Window,
|
window: Window,
|
||||||
fullscreen: bool,
|
fullscreen: bool,
|
||||||
|
|
@ -939,7 +914,6 @@ pub enum ClientMessage<'a> {
|
||||||
SetAutotile {
|
SetAutotile {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
},
|
},
|
||||||
GetAutotile,
|
|
||||||
SetTabTitleAlign {
|
SetTabTitleAlign {
|
||||||
align: u32,
|
align: u32,
|
||||||
},
|
},
|
||||||
|
|
@ -1206,9 +1180,6 @@ pub enum Response {
|
||||||
GetCornerRadius {
|
GetCornerRadius {
|
||||||
radius: f32,
|
radius: f32,
|
||||||
},
|
},
|
||||||
GetAutotile {
|
|
||||||
enabled: bool,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -466,33 +466,6 @@ impl Seat {
|
||||||
get!().set_seat_workspace(self, workspace)
|
get!().set_seat_workspace(self, workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends the currently focused window to a scratchpad.
|
|
||||||
///
|
|
||||||
/// Use an empty string for the default scratchpad.
|
|
||||||
pub fn send_to_scratchpad(self, name: &str) {
|
|
||||||
get!().seat_send_to_scratchpad(self, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggles a scratchpad.
|
|
||||||
///
|
|
||||||
/// If the scratchpad has a visible window, that window is hidden. Otherwise, the
|
|
||||||
/// most recently hidden window in the scratchpad is shown on the current workspace.
|
|
||||||
/// Scratchpad windows are always shown floating.
|
|
||||||
/// Use an empty string for the default scratchpad.
|
|
||||||
pub fn toggle_scratchpad(self, name: &str) {
|
|
||||||
get!().seat_toggle_scratchpad(self, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cycles through the windows of a scratchpad, one at a time.
|
|
||||||
///
|
|
||||||
/// With nothing shown, the first window is brought up; each further invocation
|
|
||||||
/// hides the current window and shows the next; after the last window the
|
|
||||||
/// scratchpad is hidden again. Scratchpad windows are always shown floating.
|
|
||||||
/// Use an empty string for the default scratchpad.
|
|
||||||
pub fn cycle_scratchpad(self, name: &str) {
|
|
||||||
get!().seat_cycle_scratchpad(self, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggles whether the currently focused window is fullscreen.
|
/// Toggles whether the currently focused window is fullscreen.
|
||||||
pub fn toggle_fullscreen(self) {
|
pub fn toggle_fullscreen(self) {
|
||||||
let c = get!();
|
let c = get!();
|
||||||
|
|
|
||||||
|
|
@ -115,15 +115,6 @@ impl AnimationCurve {
|
||||||
pub const EASE_IN_OUT: Self = Self(4);
|
pub const EASE_IN_OUT: Self = Self(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The presentation style used for tiled window movement animations.
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct AnimationStyle(pub u32);
|
|
||||||
|
|
||||||
impl AnimationStyle {
|
|
||||||
pub const PLAIN: Self = Self(0);
|
|
||||||
pub const MULTIPHASE: Self = Self(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Exits the compositor.
|
/// Exits the compositor.
|
||||||
pub fn quit() {
|
pub fn quit() {
|
||||||
get!().quit()
|
get!().quit()
|
||||||
|
|
@ -329,21 +320,6 @@ pub fn set_animation_curve(curve: AnimationCurve) {
|
||||||
get!().set_animation_curve(curve.0);
|
get!().set_animation_curve(curve.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the presentation style used for tiled window movement animations.
|
|
||||||
///
|
|
||||||
/// The default is [`AnimationStyle::MULTIPHASE`].
|
|
||||||
pub fn set_animation_style(style: AnimationStyle) {
|
|
||||||
get!().set_animation_style(style.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets a custom cubic-bezier curve used by tiled window animations.
|
|
||||||
///
|
|
||||||
/// `x1` and `x2` must be between `0.0` and `1.0`. The curve starts at `(0, 0)`
|
|
||||||
/// and ends at `(1, 1)`.
|
|
||||||
pub fn set_animation_cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) {
|
|
||||||
get!().set_animation_cubic_bezier(x1, y1, x2, y2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enables or disables the color-management protocol.
|
/// Enables or disables the color-management protocol.
|
||||||
///
|
///
|
||||||
/// The default is `false`.
|
/// The default is `false`.
|
||||||
|
|
@ -453,21 +429,14 @@ pub fn get_corner_radius() -> f32 {
|
||||||
|
|
||||||
/// Enables or disables autotiling.
|
/// Enables or disables autotiling.
|
||||||
///
|
///
|
||||||
/// When enabled, newly tiled windows alternate split orientation from the
|
/// When enabled, new windows are automatically placed in a perpendicular
|
||||||
/// focused tiled window: the first split uses the containing group's direction,
|
/// sub-container if the predicted body would be narrower than tall (or vice versa).
|
||||||
/// then subsequent splits wrap the focused window in the perpendicular
|
|
||||||
/// direction.
|
|
||||||
///
|
///
|
||||||
/// The default is `false`.
|
/// The default is `false`.
|
||||||
pub fn set_autotile(enabled: bool) {
|
pub fn set_autotile(enabled: bool) {
|
||||||
get!().set_autotile(enabled)
|
get!().set_autotile(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether autotiling is enabled.
|
|
||||||
pub fn get_autotile() -> bool {
|
|
||||||
get!(false).get_autotile()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the horizontal alignment of title text within tab buttons.
|
/// Sets the horizontal alignment of title text within tab buttons.
|
||||||
///
|
///
|
||||||
/// - `"start"` — left-aligned (default)
|
/// - `"start"` — left-aligned (default)
|
||||||
|
|
|
||||||
|
|
@ -205,13 +205,6 @@ impl Window {
|
||||||
get!().set_window_workspace(self, workspace)
|
get!().set_window_workspace(self, workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends the window to a scratchpad.
|
|
||||||
///
|
|
||||||
/// Use an empty string for the default scratchpad.
|
|
||||||
pub fn send_to_scratchpad(self, name: &str) {
|
|
||||||
get!().window_send_to_scratchpad(self, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggles whether the currently focused window is fullscreen.
|
/// Toggles whether the currently focused window is fullscreen.
|
||||||
pub fn toggle_fullscreen(self) {
|
pub fn toggle_fullscreen(self) {
|
||||||
self.set_fullscreen(!self.fullscreen())
|
self.set_fullscreen(!self.fullscreen())
|
||||||
|
|
|
||||||
1008
src/animation.rs
1008
src/animation.rs
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -363,9 +363,6 @@ fn start_compositor2(
|
||||||
animations: Default::default(),
|
animations: Default::default(),
|
||||||
layout_animations_requested: Default::default(),
|
layout_animations_requested: Default::default(),
|
||||||
layout_animations_active: Default::default(),
|
layout_animations_active: Default::default(),
|
||||||
layout_animation_curve_override: Default::default(),
|
|
||||||
layout_animation_style_override: Default::default(),
|
|
||||||
layout_animation_batch: Default::default(),
|
|
||||||
suppress_animations_for_next_layout: Default::default(),
|
suppress_animations_for_next_layout: Default::default(),
|
||||||
toplevels: Default::default(),
|
toplevels: Default::default(),
|
||||||
const_40hz_latch: Default::default(),
|
const_40hz_latch: Default::default(),
|
||||||
|
|
@ -403,7 +400,6 @@ fn start_compositor2(
|
||||||
bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)),
|
bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)),
|
||||||
virtual_outputs: Default::default(),
|
virtual_outputs: Default::default(),
|
||||||
clean_logs_older_than: Default::default(),
|
clean_logs_older_than: Default::default(),
|
||||||
scratchpads: Default::default(),
|
|
||||||
});
|
});
|
||||||
state.tracker.register(ClientId::from_raw(0));
|
state.tracker.register(ClientId::from_raw(0));
|
||||||
create_dummy_output(&state);
|
create_dummy_output(&state);
|
||||||
|
|
|
||||||
|
|
@ -668,9 +668,7 @@ impl ConfigProxyHandler {
|
||||||
fn handle_window_move(&self, window: Window, direction: Direction) -> Result<(), CphError> {
|
fn handle_window_move(&self, window: Window, direction: Direction) -> Result<(), CphError> {
|
||||||
self.state.with_layout_animations(|| {
|
self.state.with_layout_animations(|| {
|
||||||
let window = self.get_window(window)?;
|
let window = self.get_window(window)?;
|
||||||
if let Some(float) = window.tl_data().float.get() {
|
if let Some(c) = toplevel_parent_container(&*window) {
|
||||||
float.move_by_direction(direction.into());
|
|
||||||
} else if let Some(c) = toplevel_parent_container(&*window) {
|
|
||||||
c.move_child(window, direction.into());
|
c.move_child(window, direction.into());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -1005,18 +1003,6 @@ impl ConfigProxyHandler {
|
||||||
self.state.set_animation_curve(curve);
|
self.state.set_animation_curve(curve);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_set_animation_style(&self, style: u32) {
|
|
||||||
if !self.state.set_animation_style(style) {
|
|
||||||
log::warn!("Ignoring invalid animation style");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) {
|
|
||||||
if !self.state.set_animation_cubic_bezier(x1, y1, x2, y2) {
|
|
||||||
log::warn!("Ignoring invalid animation cubic-bezier curve");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_set_direct_scanout_enabled(
|
fn handle_set_direct_scanout_enabled(
|
||||||
&self,
|
&self,
|
||||||
device: Option<DrmDevice>,
|
device: Option<DrmDevice>,
|
||||||
|
|
@ -1100,32 +1086,6 @@ impl ConfigProxyHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_seat_send_to_scratchpad(&self, seat: Seat, name: &str) -> Result<(), CphError> {
|
|
||||||
self.state.with_linear_layout_animations(|| {
|
|
||||||
let seat = self.get_seat(seat)?;
|
|
||||||
if let Some(toplevel) = seat.get_keyboard_node().node_toplevel() {
|
|
||||||
self.state.send_to_scratchpad(name, toplevel);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_seat_toggle_scratchpad(&self, seat: Seat, name: &str) -> Result<(), CphError> {
|
|
||||||
self.state.with_linear_layout_animations(|| {
|
|
||||||
let seat = self.get_seat(seat)?;
|
|
||||||
self.state.toggle_scratchpad(&seat, name);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_seat_cycle_scratchpad(&self, seat: Seat, name: &str) -> Result<(), CphError> {
|
|
||||||
self.state.with_linear_layout_animations(|| {
|
|
||||||
let seat = self.get_seat(seat)?;
|
|
||||||
self.state.cycle_scratchpad(&seat, name);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_set_window_workspace(&self, window: Window, ws: Workspace) -> Result<(), CphError> {
|
fn handle_set_window_workspace(&self, window: Window, ws: Workspace) -> Result<(), CphError> {
|
||||||
let window = self.get_window(window)?;
|
let window = self.get_window(window)?;
|
||||||
let name = self.get_workspace(ws)?;
|
let name = self.get_workspace(ws)?;
|
||||||
|
|
@ -1140,14 +1100,6 @@ impl ConfigProxyHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_window_send_to_scratchpad(&self, window: Window, name: &str) -> Result<(), CphError> {
|
|
||||||
self.state.with_linear_layout_animations(|| {
|
|
||||||
let window = self.get_window(window)?;
|
|
||||||
self.state.send_to_scratchpad(name, window);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_get_device_name(&self, device: InputDevice) -> Result<(), CphError> {
|
fn handle_get_device_name(&self, device: InputDevice) -> Result<(), CphError> {
|
||||||
let dev = self.get_device_handler_data(device)?;
|
let dev = self.get_device_handler_data(device)?;
|
||||||
let name = dev.device.name();
|
let name = dev.device.name();
|
||||||
|
|
@ -2038,11 +1990,9 @@ impl ConfigProxyHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_set_seat_floating(&self, seat: Seat, floating: bool) -> Result<(), CphError> {
|
fn handle_set_seat_floating(&self, seat: Seat, floating: bool) -> Result<(), CphError> {
|
||||||
self.state.with_linear_layout_animations(|| {
|
let seat = self.get_seat(seat)?;
|
||||||
let seat = self.get_seat(seat)?;
|
seat.set_floating(floating);
|
||||||
seat.set_floating(floating);
|
Ok(())
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_get_window_floating(&self, window: Window) -> Result<(), CphError> {
|
fn handle_get_window_floating(&self, window: Window) -> Result<(), CphError> {
|
||||||
|
|
@ -2054,11 +2004,9 @@ impl ConfigProxyHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_set_window_floating(&self, window: Window, floating: bool) -> Result<(), CphError> {
|
fn handle_set_window_floating(&self, window: Window, floating: bool) -> Result<(), CphError> {
|
||||||
self.state.with_linear_layout_animations(|| {
|
let window = self.get_window(window)?;
|
||||||
let window = self.get_window(window)?;
|
toplevel_set_floating(&self.state, window, floating);
|
||||||
toplevel_set_floating(&self.state, window, floating);
|
Ok(())
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_add_pollable(self: &Rc<Self>, fd: i32) -> Result<(), CphError> {
|
fn handle_add_pollable(self: &Rc<Self>, fd: i32) -> Result<(), CphError> {
|
||||||
|
|
@ -3023,15 +2971,6 @@ impl ConfigProxyHandler {
|
||||||
ClientMessage::SetSeatWorkspace { seat, workspace } => self
|
ClientMessage::SetSeatWorkspace { seat, workspace } => self
|
||||||
.handle_set_seat_workspace(seat, workspace)
|
.handle_set_seat_workspace(seat, workspace)
|
||||||
.wrn("set_seat_workspace")?,
|
.wrn("set_seat_workspace")?,
|
||||||
ClientMessage::SeatSendToScratchpad { seat, name } => self
|
|
||||||
.handle_seat_send_to_scratchpad(seat, name)
|
|
||||||
.wrn("seat_send_to_scratchpad")?,
|
|
||||||
ClientMessage::SeatToggleScratchpad { seat, name } => self
|
|
||||||
.handle_seat_toggle_scratchpad(seat, name)
|
|
||||||
.wrn("seat_toggle_scratchpad")?,
|
|
||||||
ClientMessage::SeatCycleScratchpad { seat, name } => self
|
|
||||||
.handle_seat_cycle_scratchpad(seat, name)
|
|
||||||
.wrn("seat_cycle_scratchpad")?,
|
|
||||||
ClientMessage::GetConnector { ty, idx } => {
|
ClientMessage::GetConnector { ty, idx } => {
|
||||||
self.handle_get_connector(ty, idx).wrn("get_connector")?
|
self.handle_get_connector(ty, idx).wrn("get_connector")?
|
||||||
}
|
}
|
||||||
|
|
@ -3298,10 +3237,6 @@ impl ConfigProxyHandler {
|
||||||
self.handle_set_animation_duration_ms(duration_ms)
|
self.handle_set_animation_duration_ms(duration_ms)
|
||||||
}
|
}
|
||||||
ClientMessage::SetAnimationCurve { curve } => self.handle_set_animation_curve(curve),
|
ClientMessage::SetAnimationCurve { curve } => self.handle_set_animation_curve(curve),
|
||||||
ClientMessage::SetAnimationStyle { style } => self.handle_set_animation_style(style),
|
|
||||||
ClientMessage::SetAnimationCubicBezier { x1, y1, x2, y2 } => {
|
|
||||||
self.handle_set_animation_cubic_bezier(x1, y1, x2, y2)
|
|
||||||
}
|
|
||||||
ClientMessage::SetXScalingMode { mode } => self
|
ClientMessage::SetXScalingMode { mode } => self
|
||||||
.handle_set_x_scaling_mode(mode)
|
.handle_set_x_scaling_mode(mode)
|
||||||
.wrn("set_x_scaling_mode")?,
|
.wrn("set_x_scaling_mode")?,
|
||||||
|
|
@ -3416,9 +3351,6 @@ impl ConfigProxyHandler {
|
||||||
ClientMessage::SetWindowWorkspace { window, workspace } => self
|
ClientMessage::SetWindowWorkspace { window, workspace } => self
|
||||||
.handle_set_window_workspace(window, workspace)
|
.handle_set_window_workspace(window, workspace)
|
||||||
.wrn("set_window_workspace")?,
|
.wrn("set_window_workspace")?,
|
||||||
ClientMessage::WindowSendToScratchpad { window, name } => self
|
|
||||||
.handle_window_send_to_scratchpad(window, name)
|
|
||||||
.wrn("window_send_to_scratchpad")?,
|
|
||||||
ClientMessage::SetWindowFullscreen { window, fullscreen } => self
|
ClientMessage::SetWindowFullscreen { window, fullscreen } => self
|
||||||
.handle_set_window_fullscreen(window, fullscreen)
|
.handle_set_window_fullscreen(window, fullscreen)
|
||||||
.wrn("set_window_fullscreen")?,
|
.wrn("set_window_fullscreen")?,
|
||||||
|
|
@ -3633,11 +3565,6 @@ impl ConfigProxyHandler {
|
||||||
ClientMessage::SetAutotile { enabled } => {
|
ClientMessage::SetAutotile { enabled } => {
|
||||||
self.state.theme.autotile_enabled.set(enabled);
|
self.state.theme.autotile_enabled.set(enabled);
|
||||||
}
|
}
|
||||||
ClientMessage::GetAutotile => {
|
|
||||||
self.respond(Response::GetAutotile {
|
|
||||||
enabled: self.state.theme.autotile_enabled.get(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ClientMessage::SeatToggleExpand { .. } => {
|
ClientMessage::SeatToggleExpand { .. } => {
|
||||||
// Removed feature; kept for binary protocol compatibility.
|
// Removed feature; kept for binary protocol compatibility.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -936,9 +936,6 @@ impl WlSeatGlobal {
|
||||||
{
|
{
|
||||||
c.move_child(tl, direction);
|
c.move_child(tl, direction);
|
||||||
self.maybe_schedule_warp_mouse_to_focus();
|
self.maybe_schedule_warp_mouse_to_focus();
|
||||||
} else if let Some(float) = data.float.get() {
|
|
||||||
float.move_by_direction(direction);
|
|
||||||
self.maybe_schedule_warp_mouse_to_focus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1520,25 +1520,25 @@ impl WlSurface {
|
||||||
let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds());
|
let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds());
|
||||||
let pos = self.buffer_abs_pos.get();
|
let pos = self.buffer_abs_pos.get();
|
||||||
let apply_damage = |pos: Rect| {
|
let apply_damage = |pos: Rect| {
|
||||||
let clip_damage = |mut damage: Rect| {
|
if pending.damage_full {
|
||||||
damage = damage.intersect(pos);
|
let mut damage = pos;
|
||||||
if let Some(bounds) = bounds {
|
if let Some(bounds) = bounds {
|
||||||
damage = damage.intersect(bounds);
|
damage = damage.intersect(bounds);
|
||||||
}
|
}
|
||||||
damage
|
self.client.state.damage(damage);
|
||||||
};
|
|
||||||
if pending.damage_full {
|
|
||||||
self.client.state.damage(clip_damage(pos));
|
|
||||||
} else {
|
} else {
|
||||||
let matrix = self.damage_matrix.get();
|
let matrix = self.damage_matrix.get();
|
||||||
if let Some(buffer) = self.buffer.get() {
|
if let Some(buffer) = self.buffer.get() {
|
||||||
for damage in &pending.buffer_damage {
|
for damage in &pending.buffer_damage {
|
||||||
let damage = matrix.apply(
|
let mut damage = matrix.apply(
|
||||||
pos.x1(),
|
pos.x1(),
|
||||||
pos.y1(),
|
pos.y1(),
|
||||||
damage.intersect(buffer.buffer.buf.rect),
|
damage.intersect(buffer.buffer.buf.rect),
|
||||||
);
|
);
|
||||||
self.client.state.damage(clip_damage(damage));
|
if let Some(bounds) = bounds {
|
||||||
|
damage = damage.intersect(bounds);
|
||||||
|
}
|
||||||
|
self.client.state.damage(damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for damage in &pending.surface_damage {
|
for damage in &pending.surface_damage {
|
||||||
|
|
@ -1550,7 +1550,8 @@ impl WlSurface {
|
||||||
let y2 = (damage.y2() + scale - 1) / scale;
|
let y2 = (damage.y2() + scale - 1) / scale;
|
||||||
damage = Rect::new_saturating(x1, y1, x2, y2);
|
damage = Rect::new_saturating(x1, y1, x2, y2);
|
||||||
}
|
}
|
||||||
self.client.state.damage(clip_damage(damage));
|
damage = damage.intersect(bounds.unwrap_or(pos));
|
||||||
|
self.client.state.damage(damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -628,11 +628,6 @@ fn schedule_async_upload(
|
||||||
{
|
{
|
||||||
back_tex_opt = None;
|
back_tex_opt = None;
|
||||||
}
|
}
|
||||||
if let Some(back_tex) = &back_tex_opt
|
|
||||||
&& Rc::strong_count(back_tex) > 1
|
|
||||||
{
|
|
||||||
back_tex_opt = None;
|
|
||||||
}
|
|
||||||
let damage_full = || {
|
let damage_full = || {
|
||||||
back.damage.clear();
|
back.damage.clear();
|
||||||
back.damage.damage(slice::from_ref(&buf.rect));
|
back.damage.damage(slice::from_ref(&buf.rect));
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
ifs::wl_surface::{
|
ifs::wl_surface::{
|
||||||
PendingState, SurfaceExt, WlSurface, WlSurfaceError,
|
SurfaceExt, WlSurface, WlSurfaceError,
|
||||||
x_surface::{xwayland_surface_v1::XwaylandSurfaceV1, xwindow::Xwindow},
|
x_surface::{xwayland_surface_v1::XwaylandSurfaceV1, xwindow::Xwindow},
|
||||||
},
|
},
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
|
|
@ -30,22 +30,6 @@ impl SurfaceExt for XSurface {
|
||||||
win.node_layer()
|
win.node_layer()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn before_apply_commit(
|
|
||||||
self: Rc<Self>,
|
|
||||||
pending: &mut PendingState,
|
|
||||||
) -> Result<(), WlSurfaceError> {
|
|
||||||
if pending
|
|
||||||
.buffer
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|buffer| buffer.is_none())
|
|
||||||
&& self.surface.buffer.is_some()
|
|
||||||
&& let Some(xwindow) = self.xwindow.get()
|
|
||||||
{
|
|
||||||
xwindow.queue_spawn_out();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn after_apply_commit(self: Rc<Self>) {
|
fn after_apply_commit(self: Rc<Self>) {
|
||||||
if let Some(xwindow) = self.xwindow.get() {
|
if let Some(xwindow) = self.xwindow.get() {
|
||||||
xwindow.map_status_changed();
|
xwindow.map_status_changed();
|
||||||
|
|
@ -61,7 +45,6 @@ impl SurfaceExt for XSurface {
|
||||||
}
|
}
|
||||||
self.surface.unset_ext();
|
self.surface.unset_ext();
|
||||||
if let Some(xwindow) = self.xwindow.take() {
|
if let Some(xwindow) = self.xwindow.take() {
|
||||||
xwindow.queue_spawn_out();
|
|
||||||
xwindow.tl_destroy();
|
xwindow.tl_destroy();
|
||||||
xwindow.data.window.set(None);
|
xwindow.data.window.set(None);
|
||||||
xwindow.data.surface_id.set(None);
|
xwindow.data.surface_id.set(None);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
animation::RetainedToplevel,
|
|
||||||
client::Client,
|
client::Client,
|
||||||
cursor::KnownCursor,
|
cursor::KnownCursor,
|
||||||
fixed::Fixed,
|
fixed::Fixed,
|
||||||
|
|
@ -253,11 +252,6 @@ impl Xwindow {
|
||||||
self.x.surface.buffer.is_some() && self.data.info.mapped.get()
|
self.x.surface.buffer.is_some() && self.data.info.mapped.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_spawn_out(&self) {
|
|
||||||
self.toplevel_data
|
|
||||||
.queue_spawn_out(self, self.tl_animation_snapshot());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_change(&self) -> Change {
|
fn map_change(&self) -> Change {
|
||||||
match (self.may_be_mapped(), self.is_mapped()) {
|
match (self.may_be_mapped(), self.is_mapped()) {
|
||||||
(true, false) => Change::Map,
|
(true, false) => Change::Map,
|
||||||
|
|
@ -280,7 +274,6 @@ impl Xwindow {
|
||||||
match map_change {
|
match map_change {
|
||||||
Change::None => return,
|
Change::None => return,
|
||||||
Change::Unmap => {
|
Change::Unmap => {
|
||||||
self.queue_spawn_out();
|
|
||||||
self.data
|
self.data
|
||||||
.info
|
.info
|
||||||
.pending_extents
|
.pending_extents
|
||||||
|
|
@ -521,10 +514,6 @@ impl ToplevelNodeBase for Xwindow {
|
||||||
Some(self.x.surface.clone())
|
Some(self.x.surface.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tl_animation_snapshot(&self) -> Option<Rc<RetainedToplevel>> {
|
|
||||||
RetainedToplevel::capture_surface(&self.x.surface, (0, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tl_admits_children(&self) -> bool {
|
fn tl_admits_children(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -226,10 +226,6 @@ pub trait XdgSurfaceExt: Debug {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_unmap(&self) {
|
|
||||||
// nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extents_changed(&self) {
|
fn extents_changed(&self) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
@ -668,15 +664,6 @@ impl SurfaceExt for XdgSurface {
|
||||||
if let Some(serial) = pending.serial.take() {
|
if let Some(serial) = pending.serial.take() {
|
||||||
self.applied_serial.set(serial);
|
self.applied_serial.set(serial);
|
||||||
}
|
}
|
||||||
if pending
|
|
||||||
.buffer
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|buffer| buffer.is_none())
|
|
||||||
&& self.surface.buffer.is_some()
|
|
||||||
&& let Some(ext) = self.ext.get()
|
|
||||||
{
|
|
||||||
ext.prepare_unmap();
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ pub mod xdg_dialog_v1;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
animation::RetainedToplevel,
|
|
||||||
bugs,
|
bugs,
|
||||||
bugs::Bugs,
|
bugs::Bugs,
|
||||||
client::{Client, ClientError},
|
client::{Client, ClientError},
|
||||||
|
|
@ -260,7 +259,6 @@ impl XdgToplevelRequestHandler for XdgToplevel {
|
||||||
type Error = XdgToplevelError;
|
type Error = XdgToplevelError;
|
||||||
|
|
||||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
self.queue_spawn_out();
|
|
||||||
self.tl_destroy();
|
self.tl_destroy();
|
||||||
self.xdg.unset_ext();
|
self.xdg.unset_ext();
|
||||||
{
|
{
|
||||||
|
|
@ -400,11 +398,6 @@ impl XdgToplevelRequestHandler for XdgToplevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XdgToplevel {
|
impl XdgToplevel {
|
||||||
fn queue_spawn_out(&self) {
|
|
||||||
self.toplevel_data
|
|
||||||
.queue_spawn_out(self, self.tl_animation_snapshot());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map(
|
fn map(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
parent: Option<&XdgToplevel>,
|
parent: Option<&XdgToplevel>,
|
||||||
|
|
@ -786,11 +779,6 @@ impl ToplevelNodeBase for XdgToplevel {
|
||||||
Some(self.xdg.surface.clone())
|
Some(self.xdg.surface.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tl_animation_snapshot(&self) -> Option<Rc<RetainedToplevel>> {
|
|
||||||
let geo = self.xdg.geometry();
|
|
||||||
RetainedToplevel::capture_surface(&self.xdg.surface, (-geo.x1(), -geo.y1()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tl_restack_popups(&self) {
|
fn tl_restack_popups(&self) {
|
||||||
self.xdg.restack_popups();
|
self.xdg.restack_popups();
|
||||||
}
|
}
|
||||||
|
|
@ -830,10 +818,6 @@ impl XdgSurfaceExt for XdgToplevel {
|
||||||
self.after_commit(None);
|
self.after_commit(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_unmap(&self) {
|
|
||||||
self.queue_spawn_out();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extents_changed(&self) {
|
fn extents_changed(&self) {
|
||||||
self.toplevel_data.pos.set(self.xdg.extents.get());
|
self.toplevel_data.pos.set(self.xdg.extents.get());
|
||||||
self.tl_extents_changed();
|
self.tl_extents_changed();
|
||||||
|
|
|
||||||
|
|
@ -284,27 +284,6 @@ impl TestConfig {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_to_scratchpad(&self, seat: SeatId, name: &str) -> TestResult {
|
|
||||||
self.send(ClientMessage::SeatSendToScratchpad {
|
|
||||||
seat: Seat(seat.raw() as _),
|
|
||||||
name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle_scratchpad(&self, seat: SeatId, name: &str) -> TestResult {
|
|
||||||
self.send(ClientMessage::SeatToggleScratchpad {
|
|
||||||
seat: Seat(seat.raw() as _),
|
|
||||||
name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cycle_scratchpad(&self, seat: SeatId, name: &str) -> TestResult {
|
|
||||||
self.send(ClientMessage::SeatCycleScratchpad {
|
|
||||||
seat: Seat(seat.raw() as _),
|
|
||||||
name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&self) {
|
fn clear(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
if let Some(srv) = self.srv.take() {
|
if let Some(srv) = self.srv.take() {
|
||||||
|
|
@ -352,10 +331,6 @@ impl TestConfig {
|
||||||
pub fn set_show_titles(&self, show: bool) -> TestResult {
|
pub fn set_show_titles(&self, show: bool) -> TestResult {
|
||||||
self.send(ClientMessage::SetShowTitles { show })
|
self.send(ClientMessage::SetShowTitles { show })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_autotile(&self, enabled: bool) -> TestResult {
|
|
||||||
self.send(ClientMessage::SetAutotile { enabled })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TestConfig {
|
impl Drop for TestConfig {
|
||||||
|
|
|
||||||
|
|
@ -29,17 +29,6 @@ impl TestViewport {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unset_source(&self) -> Result<(), TestError> {
|
|
||||||
self.tran.send(SetSource {
|
|
||||||
self_id: self.id,
|
|
||||||
x: Fixed::from_int(-1),
|
|
||||||
y: Fixed::from_int(-1),
|
|
||||||
width: Fixed::from_int(-1),
|
|
||||||
height: Fixed::from_int(-1),
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_destination(&self, width: i32, height: i32) -> Result<(), TestError> {
|
pub fn set_destination(&self, width: i32, height: i32) -> Result<(), TestError> {
|
||||||
self.tran.send(SetDestination {
|
self.tran.send(SetDestination {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
|
|
@ -48,15 +37,6 @@ impl TestViewport {
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unset_destination(&self) -> Result<(), TestError> {
|
|
||||||
self.tran.send(SetDestination {
|
|
||||||
self_id: self.id,
|
|
||||||
width: -1,
|
|
||||||
height: -1,
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TestViewport {
|
impl Drop for TestViewport {
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,6 @@ mod t0051_pointer_warp;
|
||||||
mod t0052_bar;
|
mod t0052_bar;
|
||||||
mod t0053_theme;
|
mod t0053_theme;
|
||||||
mod t0054_subsurface_already_attached;
|
mod t0054_subsurface_already_attached;
|
||||||
mod t0055_autotiling;
|
|
||||||
mod t0055_scratchpad;
|
|
||||||
|
|
||||||
pub trait TestCase: Sync {
|
pub trait TestCase: Sync {
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
|
|
@ -160,7 +158,5 @@ pub fn tests() -> Vec<&'static dyn TestCase> {
|
||||||
t0052_bar,
|
t0052_bar,
|
||||||
t0053_theme,
|
t0053_theme,
|
||||||
t0054_subsurface_already_attached,
|
t0054_subsurface_already_attached,
|
||||||
t0055_autotiling,
|
|
||||||
t0055_scratchpad,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
it::{test_error::TestError, testrun::TestRun},
|
it::{test_error::TestError, testrun::TestRun},
|
||||||
|
rect::Rect,
|
||||||
tree::Node,
|
tree::Node,
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
|
|
@ -10,19 +11,29 @@ testcase!();
|
||||||
|
|
||||||
/// Create and map a single surface
|
/// Create and map a single surface
|
||||||
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
||||||
let ds = run.create_default_setup().await?;
|
run.backend.install_default()?;
|
||||||
|
|
||||||
let client = run.create_client().await?;
|
let client = run.create_client().await?;
|
||||||
|
|
||||||
let window = client.create_window().await?;
|
let window = client.create_window().await?;
|
||||||
window.map().await?;
|
window.map().await?;
|
||||||
|
|
||||||
let workspace_rect = ds.output.workspace_rect.get();
|
tassert_eq!(window.tl.core.width.get(), 800);
|
||||||
|
tassert_eq!(
|
||||||
|
window.tl.core.height.get(),
|
||||||
|
600 - 2 * run.state.theme.title_plus_underline_height()
|
||||||
|
);
|
||||||
|
|
||||||
tassert_eq!(window.tl.core.width.get(), workspace_rect.width());
|
tassert_eq!(
|
||||||
tassert_eq!(window.tl.core.height.get(), workspace_rect.height());
|
window.tl.server.node_absolute_position(),
|
||||||
|
Rect::new_sized(
|
||||||
tassert_eq!(window.tl.server.node_absolute_position(), workspace_rect);
|
0,
|
||||||
|
2 * run.state.theme.title_plus_underline_height(),
|
||||||
|
window.tl.core.width.get(),
|
||||||
|
window.tl.core.height.get(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ testcase!();
|
||||||
|
|
||||||
/// Create and map two surfaces
|
/// Create and map two surfaces
|
||||||
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
||||||
let ds = run.create_default_setup().await?;
|
run.backend.install_default()?;
|
||||||
|
|
||||||
let client = run.create_client().await?;
|
let client = run.create_client().await?;
|
||||||
|
|
||||||
|
|
@ -21,30 +21,17 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
||||||
let window2 = client.create_window().await?;
|
let window2 = client.create_window().await?;
|
||||||
window2.map().await?;
|
window2.map().await?;
|
||||||
|
|
||||||
let workspace_rect = ds.output.workspace_rect.get();
|
let otop = 2 * run.state.theme.title_plus_underline_height();
|
||||||
let bw = run.state.theme.sizes.border_width.get();
|
let bw = run.state.theme.sizes.border_width.get();
|
||||||
let child_width = (workspace_rect.width() - bw) / 2;
|
|
||||||
|
|
||||||
tassert_eq!(
|
tassert_eq!(
|
||||||
window.tl.server.node_absolute_position(),
|
window.tl.server.node_absolute_position(),
|
||||||
Rect::new_sized(
|
Rect::new_sized(0, otop, (800 - bw) / 2, 600 - otop).unwrap()
|
||||||
workspace_rect.x1(),
|
|
||||||
workspace_rect.y1(),
|
|
||||||
child_width,
|
|
||||||
workspace_rect.height(),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
tassert_eq!(
|
tassert_eq!(
|
||||||
window2.tl.server.node_absolute_position(),
|
window2.tl.server.node_absolute_position(),
|
||||||
Rect::new_sized(
|
Rect::new_sized((800 - bw) / 2 + bw, otop, (800 - bw) / 2, 600 - otop).unwrap()
|
||||||
workspace_rect.x1() + child_width + bw,
|
|
||||||
workspace_rect.y1(),
|
|
||||||
child_width,
|
|
||||||
workspace_rect.height(),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -48,18 +48,13 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
|
|
||||||
let mono_container = w_mono2.tl.container_parent()?;
|
let mono_container = w_mono2.tl.container_parent()?;
|
||||||
let container_pos = mono_container.tl_data().pos.get();
|
let container_pos = mono_container.tl_data().pos.get();
|
||||||
let (tab_x, tab_y) = {
|
let w_mono1_title = mono_container.render_data.borrow_mut().title_rects[0]
|
||||||
let tab_bar = mono_container.tab_bar.borrow();
|
.move_(container_pos.x1(), container_pos.y1());
|
||||||
let Some(tab_bar) = tab_bar.as_ref() else {
|
ds.mouse.abs(
|
||||||
bail!("no tab bar");
|
&ds.connector,
|
||||||
};
|
w_mono1_title.x1() as _,
|
||||||
let w_mono1_title = &tab_bar.entries[0];
|
w_mono1_title.y1() as _,
|
||||||
(
|
);
|
||||||
container_pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2,
|
|
||||||
container_pos.y1() + tab_bar.height / 2,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
ds.mouse.abs(&ds.connector, tab_x as _, tab_y as _);
|
|
||||||
|
|
||||||
client.sync().await;
|
client.sync().await;
|
||||||
tassert!(enters.next().is_err());
|
tassert!(enters.next().is_err());
|
||||||
|
|
|
||||||
|
|
@ -26,18 +26,12 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
|
|
||||||
let container = w_mono2.tl.container_parent()?;
|
let container = w_mono2.tl.container_parent()?;
|
||||||
let pos = container.tl_data().pos.get();
|
let pos = container.tl_data().pos.get();
|
||||||
let (tab_x, tab_y) = {
|
let w_mono1_title = container.render_data.borrow_mut().title_rects[0].move_(pos.x1(), pos.y1());
|
||||||
let tab_bar = container.tab_bar.borrow();
|
ds.mouse.abs(
|
||||||
let Some(tab_bar) = tab_bar.as_ref() else {
|
&ds.connector,
|
||||||
bail!("no tab bar");
|
w_mono1_title.x1() as f64,
|
||||||
};
|
w_mono1_title.y1() as f64,
|
||||||
let w_mono1_title = &tab_bar.entries[0];
|
);
|
||||||
(
|
|
||||||
pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2,
|
|
||||||
pos.y1() + tab_bar.height / 2,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
ds.mouse.abs(&ds.connector, tab_x as f64, tab_y as f64);
|
|
||||||
client.sync().await;
|
client.sync().await;
|
||||||
|
|
||||||
let enters = dss.kb.enter.expect()?;
|
let enters = dss.kb.enter.expect()?;
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -2,7 +2,7 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED,
|
ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED,
|
||||||
it::{
|
it::{
|
||||||
test_error::{TestErrorExt, TestResult},
|
test_error::TestResult,
|
||||||
test_utils::{
|
test_utils::{
|
||||||
test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt,
|
test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt,
|
||||||
},
|
},
|
||||||
|
|
@ -10,7 +10,7 @@ use {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
isnt::std_1::collections::IsntHashSetExt,
|
isnt::std_1::collections::IsntHashSetExt,
|
||||||
std::{rc::Rc, time::Duration},
|
std::rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
testcase!();
|
testcase!();
|
||||||
|
|
@ -19,7 +19,6 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
let ds = run.create_default_setup().await?;
|
let ds = run.create_default_setup().await?;
|
||||||
|
|
||||||
let client = run.create_client().await?;
|
let client = run.create_client().await?;
|
||||||
let default_seat = client.get_default_seat().await?;
|
|
||||||
|
|
||||||
let win1 = client.create_window().await?;
|
let win1 = client.create_window().await?;
|
||||||
win1.set_color(255, 0, 0, 255);
|
win1.set_color(255, 0, 0, 255);
|
||||||
|
|
@ -45,23 +44,5 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
client.sync().await;
|
client.sync().await;
|
||||||
tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED));
|
tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED));
|
||||||
|
|
||||||
let leaves = default_seat.kb.leave.expect()?;
|
|
||||||
let enters = default_seat.kb.enter.expect()?;
|
|
||||||
|
|
||||||
run.cfg.set_idle(Duration::from_micros(100))?;
|
|
||||||
run.cfg.set_idle_grace_period(Duration::from_secs(0))?;
|
|
||||||
run.state.wheel.timeout(3).await?;
|
|
||||||
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(win2.tl.core.states.borrow().contains(&STATE_SUSPENDED));
|
|
||||||
let leave = leaves.next().with_context(|| "no leave on suspend")?;
|
|
||||||
tassert_eq!(leave.surface, win2.surface.id);
|
|
||||||
|
|
||||||
ds.mouse.rel(1.0, 1.0);
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED));
|
|
||||||
let enter = enters.next().with_context(|| "no enter on restore")?;
|
|
||||||
tassert_eq!(enter.surface, win2.surface.id);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -308,8 +308,9 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
let output_damage = connector_data.damage.borrow();
|
let output_damage = connector_data.damage.borrow();
|
||||||
tassert!(!output_damage.is_empty());
|
tassert!(!output_damage.is_empty());
|
||||||
|
|
||||||
// The test window maps its 1x1 buffer through a viewport to the full window size.
|
// Buffer damage is transformed by the damage matrix which includes the surface position
|
||||||
let expected_buffer_damage = surface_pos;
|
// The buffer damage (0,0,1,1) should be transformed to surface coordinates
|
||||||
|
let expected_buffer_damage = buffer_damage.move_(surface_pos.x1(), surface_pos.y1());
|
||||||
|
|
||||||
// Find the exact output damage that matches our expected buffer damage
|
// Find the exact output damage that matches our expected buffer damage
|
||||||
let mut found_exact_buffer_damage = false;
|
let mut found_exact_buffer_damage = false;
|
||||||
|
|
@ -330,12 +331,10 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
// Test 7: Check output damage from existing window's viewport (which already has scaling)
|
// Test 7: Check output damage from existing window's viewport (which already has scaling)
|
||||||
connector_data.damage.borrow_mut().clear();
|
connector_data.damage.borrow_mut().clear();
|
||||||
|
|
||||||
// The existing window was created with create_surface_ext() which automatically creates a viewport.
|
// The existing window was created with create_surface_ext() which automatically creates a viewport
|
||||||
// Commit the viewport size change separately; that commit intentionally damages the old/new extents.
|
// Let's verify that the viewport's existing scaling affects buffer damage correctly
|
||||||
window.surface.viewport.set_destination(150, 100)?;
|
// First, let's modify the viewport scaling that already exists on the window
|
||||||
window.surface.commit()?;
|
window.surface.viewport.set_destination(150, 100)?; // Change scaling to 150x100
|
||||||
client.sync().await;
|
|
||||||
connector_data.damage.borrow_mut().clear();
|
|
||||||
|
|
||||||
// Add buffer damage to test viewport scaling coordinate transformation
|
// Add buffer damage to test viewport scaling coordinate transformation
|
||||||
window.surface.damage_buffer(0, 0, 1, 1)?; // Damage entire 1x1 buffer
|
window.surface.damage_buffer(0, 0, 1, 1)?; // Damage entire 1x1 buffer
|
||||||
|
|
@ -347,8 +346,8 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
let output_damage = connector_data.damage.borrow();
|
let output_damage = connector_data.damage.borrow();
|
||||||
tassert!(!output_damage.is_empty());
|
tassert!(!output_damage.is_empty());
|
||||||
|
|
||||||
// With viewporter scaling, the 1x1 buffer damage should scale to the viewport destination.
|
// With viewporter scaling, the 1x1 buffer damage should scale to 150x100
|
||||||
let surface_pos = window.surface.server.buffer_abs_pos.get();
|
// and be moved by surface position (0, 36) to get output coordinates (0, 36, 150, 136)
|
||||||
let expected_scaled_damage = Rect::new_sized(0, 0, 150, 100).unwrap();
|
let expected_scaled_damage = Rect::new_sized(0, 0, 150, 100).unwrap();
|
||||||
let expected_output_damage =
|
let expected_output_damage =
|
||||||
expected_scaled_damage.move_(surface_pos.x1(), surface_pos.y1());
|
expected_scaled_damage.move_(surface_pos.x1(), surface_pos.y1());
|
||||||
|
|
@ -403,9 +402,8 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
rotation_window.map().await?;
|
rotation_window.map().await?;
|
||||||
client.sync().await;
|
client.sync().await;
|
||||||
|
|
||||||
// Disable viewporter to rely purely on buffer dimensions.
|
// Disable viewporter by setting destination to 0x0 to rely purely on buffer dimensions
|
||||||
rotation_window.surface.viewport.unset_source()?;
|
rotation_window.surface.viewport.set_destination(0, 0)?; // Disable viewporter
|
||||||
rotation_window.surface.viewport.unset_destination()?;
|
|
||||||
|
|
||||||
// Use a rectangular buffer (4x2) so rotation has a visible geometric effect
|
// Use a rectangular buffer (4x2) so rotation has a visible geometric effect
|
||||||
// Attach AFTER mapping to avoid being overwritten by map()'s single-pixel buffer
|
// Attach AFTER mapping to avoid being overwritten by map()'s single-pixel buffer
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
it::{test_error::TestResult, testrun::TestRun},
|
|
||||||
tree::{ContainerSplit, Node, ToplevelNodeBase},
|
|
||||||
},
|
|
||||||
std::rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
testcase!();
|
|
||||||
|
|
||||||
async fn test(run: Rc<TestRun>) -> TestResult {
|
|
||||||
run.backend.install_default()?;
|
|
||||||
run.cfg.set_autotile(true)?;
|
|
||||||
|
|
||||||
let client = run.create_client().await?;
|
|
||||||
|
|
||||||
let win1 = client.create_window().await?;
|
|
||||||
win1.map().await?;
|
|
||||||
let root = win1.tl.container_parent()?;
|
|
||||||
tassert_eq!(root.split.get(), ContainerSplit::Horizontal);
|
|
||||||
|
|
||||||
let win2 = client.create_window().await?;
|
|
||||||
win2.map().await?;
|
|
||||||
client.sync().await;
|
|
||||||
|
|
||||||
tassert_eq!(root.split.get(), ContainerSplit::Horizontal);
|
|
||||||
tassert_eq!(win1.tl.container_parent()?.node_id(), root.node_id());
|
|
||||||
tassert_eq!(win2.tl.container_parent()?.node_id(), root.node_id());
|
|
||||||
|
|
||||||
let win3 = client.create_window().await?;
|
|
||||||
win3.map().await?;
|
|
||||||
client.sync().await;
|
|
||||||
|
|
||||||
let v_group = win3.tl.container_parent()?;
|
|
||||||
tassert_eq!(root.split.get(), ContainerSplit::Horizontal);
|
|
||||||
tassert_eq!(v_group.split.get(), ContainerSplit::Vertical);
|
|
||||||
tassert_eq!(win2.tl.container_parent()?.node_id(), v_group.node_id());
|
|
||||||
|
|
||||||
let win4 = client.create_window().await?;
|
|
||||||
win4.map().await?;
|
|
||||||
client.sync().await;
|
|
||||||
|
|
||||||
let h_group = win4.tl.container_parent()?;
|
|
||||||
tassert_eq!(h_group.split.get(), ContainerSplit::Horizontal);
|
|
||||||
tassert_eq!(win3.tl.container_parent()?.node_id(), h_group.node_id());
|
|
||||||
let h_parent = match h_group
|
|
||||||
.tl_data()
|
|
||||||
.parent
|
|
||||||
.get()
|
|
||||||
.and_then(|p| p.node_into_container())
|
|
||||||
{
|
|
||||||
Some(parent) => parent,
|
|
||||||
None => bail!("autotile group does not have a container parent"),
|
|
||||||
};
|
|
||||||
tassert_eq!(h_parent.node_id(), v_group.node_id());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
it::{test_error::TestResult, testrun::TestRun},
|
|
||||||
tree::{Node, ToplevelNodeBase},
|
|
||||||
},
|
|
||||||
std::rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
testcase!();
|
|
||||||
|
|
||||||
async fn test(run: Rc<TestRun>) -> TestResult {
|
|
||||||
let ds = run.create_default_setup().await?;
|
|
||||||
|
|
||||||
let client = run.create_client().await?;
|
|
||||||
let win1 = client.create_window().await?;
|
|
||||||
win1.map2().await?;
|
|
||||||
let win2 = client.create_window().await?;
|
|
||||||
win2.map2().await?;
|
|
||||||
|
|
||||||
run.cfg.send_to_scratchpad(ds.seat.id(), "term")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(win1.tl.server.node_visible());
|
|
||||||
tassert!(!win2.tl.server.node_visible());
|
|
||||||
|
|
||||||
run.cfg.show_workspace(ds.seat.id(), "2")?;
|
|
||||||
run.cfg.toggle_scratchpad(ds.seat.id(), "term")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(win2.tl.server.node_visible());
|
|
||||||
tassert_eq!(ds.output.workspace.get().unwrap().name.as_str(), "2");
|
|
||||||
|
|
||||||
run.cfg.toggle_scratchpad(ds.seat.id(), "term")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(!win2.tl.server.node_visible());
|
|
||||||
|
|
||||||
run.cfg.toggle_scratchpad(ds.seat.id(), "term")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(win2.tl.server.node_visible());
|
|
||||||
tassert_eq!(ds.output.workspace.get().unwrap().name.as_str(), "2");
|
|
||||||
|
|
||||||
run.cfg.show_workspace(ds.seat.id(), "3")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(!win2.tl.server.node_visible());
|
|
||||||
|
|
||||||
run.cfg.toggle_scratchpad(ds.seat.id(), "term")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(win2.tl.server.node_visible());
|
|
||||||
tassert_eq!(ds.output.workspace.get().unwrap().name.as_str(), "3");
|
|
||||||
// Scratchpad windows are always shown floating.
|
|
||||||
tassert!(win2.tl.server.tl_data().parent_is_float.get());
|
|
||||||
|
|
||||||
// Park win2 again, then build a multi-window scratchpad and cycle it.
|
|
||||||
run.cfg.toggle_scratchpad(ds.seat.id(), "term")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(!win2.tl.server.node_visible());
|
|
||||||
|
|
||||||
// Build a three-window scratchpad. Each window is focused right after it is
|
|
||||||
// mapped, so sending the focused window parks them in a known order.
|
|
||||||
let cyc1 = client.create_window().await?;
|
|
||||||
cyc1.map2().await?;
|
|
||||||
run.cfg.send_to_scratchpad(ds.seat.id(), "cyc")?;
|
|
||||||
let cyc2 = client.create_window().await?;
|
|
||||||
cyc2.map2().await?;
|
|
||||||
run.cfg.send_to_scratchpad(ds.seat.id(), "cyc")?;
|
|
||||||
let cyc3 = client.create_window().await?;
|
|
||||||
cyc3.map2().await?;
|
|
||||||
run.cfg.send_to_scratchpad(ds.seat.id(), "cyc")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(!cyc1.tl.server.node_visible());
|
|
||||||
tassert!(!cyc2.tl.server.node_visible());
|
|
||||||
tassert!(!cyc3.tl.server.node_visible());
|
|
||||||
|
|
||||||
// Nothing shown: cycle brings up the first window (insertion order: cyc1).
|
|
||||||
run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(cyc1.tl.server.node_visible());
|
|
||||||
tassert!(!cyc2.tl.server.node_visible());
|
|
||||||
tassert!(!cyc3.tl.server.node_visible());
|
|
||||||
// Scratchpad windows are always shown floating.
|
|
||||||
tassert!(cyc1.tl.server.tl_data().parent_is_float.get());
|
|
||||||
|
|
||||||
// Cycle advances one at a time.
|
|
||||||
run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(!cyc1.tl.server.node_visible());
|
|
||||||
tassert!(cyc2.tl.server.node_visible());
|
|
||||||
tassert!(!cyc3.tl.server.node_visible());
|
|
||||||
|
|
||||||
run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(!cyc1.tl.server.node_visible());
|
|
||||||
tassert!(!cyc2.tl.server.node_visible());
|
|
||||||
tassert!(cyc3.tl.server.node_visible());
|
|
||||||
|
|
||||||
// On the final window, the next cycle hides everything.
|
|
||||||
run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(!cyc1.tl.server.node_visible());
|
|
||||||
tassert!(!cyc2.tl.server.node_visible());
|
|
||||||
tassert!(!cyc3.tl.server.node_visible());
|
|
||||||
|
|
||||||
// And it wraps back to the first window.
|
|
||||||
run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?;
|
|
||||||
client.sync().await;
|
|
||||||
tassert!(cyc1.tl.server.node_visible());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
315
src/renderer.rs
315
src/renderer.rs
|
|
@ -1,11 +1,7 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
animation::{
|
|
||||||
RetainedContent, RetainedExitFrame, RetainedExitLayer, RetainedSurface,
|
|
||||||
RetainedToplevel,
|
|
||||||
},
|
|
||||||
cmm::cmm_render_intent::RenderIntent,
|
cmm::cmm_render_intent::RenderIntent,
|
||||||
gfx_api::{AcquireSync, AlphaMode, BufferResv, GfxApiOpt, ReleaseSync, SampleRect},
|
gfx_api::{AcquireSync, AlphaMode, GfxApiOpt, ReleaseSync, SampleRect},
|
||||||
ifs::wl_surface::{
|
ifs::wl_surface::{
|
||||||
SurfaceBuffer, WlSurface,
|
SurfaceBuffer, WlSurface,
|
||||||
x_surface::xwindow::Xwindow,
|
x_surface::xwindow::Xwindow,
|
||||||
|
|
@ -18,7 +14,7 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
theme::{Color, CornerRadius},
|
theme::{Color, CornerRadius},
|
||||||
tree::{
|
tree::{
|
||||||
ContainerNode, DisplayNode, FloatNode, Node, OutputNode, PlaceholderNode, ToplevelData,
|
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
|
||||||
ToplevelNode, ToplevelNodeBase, WorkspaceNode, tab_bar::TabBar,
|
ToplevelNode, ToplevelNodeBase, WorkspaceNode, tab_bar::TabBar,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -204,22 +200,14 @@ impl Renderer<'_> {
|
||||||
self.render_workspace(&ws, x, y);
|
self.render_workspace(&ws, x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let now = self.state.now_nsec();
|
|
||||||
let exit_frames = self.state.animations.exit_frames(now);
|
|
||||||
self.render_exit_frames(&exit_frames, RetainedExitLayer::Tiled, &opos);
|
|
||||||
macro_rules! render_stacked {
|
macro_rules! render_stacked {
|
||||||
($stack:expr) => {
|
($stack:expr) => {
|
||||||
for stacked in $stack.iter() {
|
for stacked in $stack.iter() {
|
||||||
if stacked.node_visible() {
|
if stacked.node_visible() {
|
||||||
self.base.sync();
|
self.base.sync();
|
||||||
let pos = stacked.node_absolute_position();
|
let pos = stacked.node_absolute_position();
|
||||||
let visual = self.state.animations.visual_rect(
|
if pos.intersects(&opos) {
|
||||||
stacked.node_id(),
|
let (x, y) = opos.translate(pos.x1(), pos.y1());
|
||||||
pos,
|
|
||||||
self.state.now_nsec(),
|
|
||||||
);
|
|
||||||
if visual.intersects(&opos) {
|
|
||||||
let (x, y) = opos.translate(visual.x1(), visual.y1());
|
|
||||||
stacked.node_render(self, x, y, None);
|
stacked.node_render(self, x, y, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -227,7 +215,6 @@ impl Renderer<'_> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
render_stacked!(self.state.root.stacked);
|
render_stacked!(self.state.root.stacked);
|
||||||
self.render_exit_frames(&exit_frames, RetainedExitLayer::Floating, &opos);
|
|
||||||
// Flush RoundedFillRect ops from container/float borders so they don't
|
// Flush RoundedFillRect ops from container/float borders so they don't
|
||||||
// sort after (and render on top of) layer-shell CopyTexture ops.
|
// sort after (and render on top of) layer-shell CopyTexture ops.
|
||||||
self.base.sync();
|
self.base.sync();
|
||||||
|
|
@ -480,251 +467,6 @@ impl Renderer<'_> {
|
||||||
visual.move_(-container.abs_x1.get(), -container.abs_y1.get())
|
visual.move_(-container.abs_x1.get(), -container.abs_y1.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_child_or_snapshot(
|
|
||||||
&mut self,
|
|
||||||
child: &Rc<dyn ToplevelNode>,
|
|
||||||
x: i32,
|
|
||||||
y: i32,
|
|
||||||
bounds: Option<&Rect>,
|
|
||||||
) {
|
|
||||||
if let Some(retained) = self
|
|
||||||
.state
|
|
||||||
.animations
|
|
||||||
.retained_snapshot(child.node_id(), self.state.now_nsec())
|
|
||||||
{
|
|
||||||
self.render_retained_toplevel(&retained, x, y, bounds);
|
|
||||||
} else {
|
|
||||||
child.node_render(self, x, y, bounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_retained_toplevel(
|
|
||||||
&mut self,
|
|
||||||
retained: &RetainedToplevel,
|
|
||||||
x: i32,
|
|
||||||
y: i32,
|
|
||||||
bounds: Option<&Rect>,
|
|
||||||
) {
|
|
||||||
let (x, y) = self
|
|
||||||
.base
|
|
||||||
.scale_point(x + retained.offset.0, y + retained.offset.1);
|
|
||||||
self.render_retained_surface_scaled(&retained.surface, x, y, None, bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_exit_frames(
|
|
||||||
&mut self,
|
|
||||||
frames: &[RetainedExitFrame],
|
|
||||||
layer: RetainedExitLayer,
|
|
||||||
output_rect: &Rect,
|
|
||||||
) {
|
|
||||||
for frame in frames {
|
|
||||||
if frame.layer != layer || !frame.rect.intersects(output_rect) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
self.render_exit_frame(frame, output_rect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_exit_frame(&mut self, frame: &RetainedExitFrame, output_rect: &Rect) {
|
|
||||||
let (x, y) = output_rect.translate(frame.rect.x1(), frame.rect.y1());
|
|
||||||
let inset = frame.frame_inset;
|
|
||||||
if inset > 0 {
|
|
||||||
let color = if frame.active {
|
|
||||||
self.state.theme.colors.active_border.get()
|
|
||||||
} else {
|
|
||||||
self.state.theme.colors.border.get()
|
|
||||||
};
|
|
||||||
self.render_rounded_frame(
|
|
||||||
Rect::new_sized_saturating(0, 0, frame.rect.width(), frame.rect.height()),
|
|
||||||
&color,
|
|
||||||
self.state.theme.corner_radius.get(),
|
|
||||||
inset,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let body = Rect::new_sized_saturating(
|
|
||||||
x + inset,
|
|
||||||
y + inset,
|
|
||||||
frame.rect.width() - 2 * inset,
|
|
||||||
frame.rect.height() - 2 * inset,
|
|
||||||
);
|
|
||||||
if body.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if inset > 0 && !self.state.theme.corner_radius.get().is_zero() {
|
|
||||||
let inner_cr = self.scale_corner_radius(
|
|
||||||
self.state
|
|
||||||
.theme
|
|
||||||
.corner_radius
|
|
||||||
.get()
|
|
||||||
.expanded_by(-(inset as f32)),
|
|
||||||
);
|
|
||||||
self.corner_radius = Some(inner_cr);
|
|
||||||
}
|
|
||||||
self.render_window_body_background(body);
|
|
||||||
let bounds = self.base.scale_rect(body);
|
|
||||||
self.stretch = if frame.source_body_size != body.size() {
|
|
||||||
Some(self.base.scale_point(body.width(), body.height()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
self.render_retained_toplevel(&frame.retained, body.x1(), body.y1(), Some(&bounds));
|
|
||||||
self.stretch = None;
|
|
||||||
self.corner_radius = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_window_body_background(&mut self, body: Rect) {
|
|
||||||
if body.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let color = self.state.theme.colors.background.get();
|
|
||||||
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
|
||||||
let srgb = &srgb_srgb.linear;
|
|
||||||
let perceptual = RenderIntent::Perceptual;
|
|
||||||
self.base.sync();
|
|
||||||
if let Some(cr) = self.corner_radius
|
|
||||||
&& !cr.is_zero()
|
|
||||||
{
|
|
||||||
self.base
|
|
||||||
.fill_rounded_rect(body, &color, None, srgb, perceptual, cr, 0.0);
|
|
||||||
} else {
|
|
||||||
let bounds = self.base.scale_rect(body);
|
|
||||||
self.base
|
|
||||||
.fill_scaled_boxes(slice::from_ref(&bounds), &color, None, srgb, perceptual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_retained_surface_scaled(
|
|
||||||
&mut self,
|
|
||||||
retained: &RetainedSurface,
|
|
||||||
x: i32,
|
|
||||||
y: i32,
|
|
||||||
pos_rel: Option<(i32, i32)>,
|
|
||||||
bounds: Option<&Rect>,
|
|
||||||
) {
|
|
||||||
let stretch = self.stretch.take();
|
|
||||||
let corner_radius = self.corner_radius.take();
|
|
||||||
let mut size = retained.size;
|
|
||||||
if let Some((x_rel, y_rel)) = pos_rel {
|
|
||||||
let (x, y) = self.base.scale_point(x_rel, y_rel);
|
|
||||||
let (w, h) = self.base.scale_point(x_rel + size.0, y_rel + size.1);
|
|
||||||
size = (w - x, h - y);
|
|
||||||
} else {
|
|
||||||
size = self.base.scale_point(size.0, size.1);
|
|
||||||
}
|
|
||||||
let mut stretched_source = None;
|
|
||||||
if let Some(s) = stretch {
|
|
||||||
if let RetainedContent::Texture { source, .. } = &retained.content {
|
|
||||||
let mut source = *source;
|
|
||||||
if size.0 > 0 && size.1 > 0 {
|
|
||||||
let sx = s.0 as f32 / size.0 as f32;
|
|
||||||
let sy = s.1 as f32 / size.1 as f32;
|
|
||||||
source.x2 *= sx;
|
|
||||||
source.y2 *= sy;
|
|
||||||
}
|
|
||||||
stretched_source = Some(source);
|
|
||||||
}
|
|
||||||
size = s;
|
|
||||||
}
|
|
||||||
for child in &retained.below {
|
|
||||||
let (x1, y1) = self.base.scale_point(child.offset.0, child.offset.1);
|
|
||||||
self.render_retained_surface_scaled(child, x + x1, y + y1, Some(child.offset), bounds);
|
|
||||||
}
|
|
||||||
self.corner_radius = corner_radius;
|
|
||||||
self.render_retained_content(retained, stretched_source, x, y, size, bounds);
|
|
||||||
for child in &retained.above {
|
|
||||||
let (x1, y1) = self.base.scale_point(child.offset.0, child.offset.1);
|
|
||||||
self.render_retained_surface_scaled(child, x + x1, y + y1, Some(child.offset), bounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_retained_content(
|
|
||||||
&mut self,
|
|
||||||
retained: &RetainedSurface,
|
|
||||||
stretched_source: Option<SampleRect>,
|
|
||||||
x: i32,
|
|
||||||
y: i32,
|
|
||||||
size: (i32, i32),
|
|
||||||
bounds: Option<&Rect>,
|
|
||||||
) {
|
|
||||||
let corner_radius = self.corner_radius.take();
|
|
||||||
match &retained.content {
|
|
||||||
RetainedContent::Texture {
|
|
||||||
texture,
|
|
||||||
buffer,
|
|
||||||
source,
|
|
||||||
alpha,
|
|
||||||
color_description,
|
|
||||||
render_intent,
|
|
||||||
alpha_mode,
|
|
||||||
opaque,
|
|
||||||
} => {
|
|
||||||
let source = stretched_source.unwrap_or(*source);
|
|
||||||
if let Some(cr) = corner_radius {
|
|
||||||
self.base.render_rounded_texture(
|
|
||||||
texture,
|
|
||||||
*alpha,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
Some(source),
|
|
||||||
Some(size),
|
|
||||||
self.base.scale,
|
|
||||||
bounds,
|
|
||||||
Some(buffer.clone() as Rc<dyn BufferResv>),
|
|
||||||
AcquireSync::Unnecessary,
|
|
||||||
buffer.release_sync,
|
|
||||||
color_description,
|
|
||||||
*render_intent,
|
|
||||||
*alpha_mode,
|
|
||||||
cr,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
self.base.render_texture(
|
|
||||||
texture,
|
|
||||||
*alpha,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
Some(source),
|
|
||||||
Some(size),
|
|
||||||
self.base.scale,
|
|
||||||
bounds,
|
|
||||||
Some(buffer.clone() as Rc<dyn BufferResv>),
|
|
||||||
AcquireSync::Unnecessary,
|
|
||||||
buffer.release_sync,
|
|
||||||
*opaque,
|
|
||||||
color_description,
|
|
||||||
*render_intent,
|
|
||||||
*alpha_mode,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RetainedContent::Color {
|
|
||||||
color,
|
|
||||||
alpha,
|
|
||||||
color_description,
|
|
||||||
render_intent,
|
|
||||||
} => {
|
|
||||||
if let Some(rect) = Rect::new_sized(x, y, size.0, size.1) {
|
|
||||||
let rect = match bounds {
|
|
||||||
None => rect,
|
|
||||||
Some(bounds) => rect.intersect(*bounds),
|
|
||||||
};
|
|
||||||
if !rect.is_empty() {
|
|
||||||
self.base.sync();
|
|
||||||
self.base.fill_scaled_boxes(
|
|
||||||
&[rect],
|
|
||||||
color,
|
|
||||||
*alpha,
|
|
||||||
&color_description.linear,
|
|
||||||
*render_intent,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
|
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
|
||||||
self.render_container_decorations(container, x, y);
|
self.render_container_decorations(container, x, y);
|
||||||
|
|
||||||
|
|
@ -765,6 +507,7 @@ impl Renderer<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let body = visual_mb.move_(x, y);
|
let body = visual_mb.move_(x, y);
|
||||||
|
let body = self.base.scale_rect(body);
|
||||||
let content = container
|
let content = container
|
||||||
.mono_content
|
.mono_content
|
||||||
.get()
|
.get()
|
||||||
|
|
@ -783,16 +526,9 @@ impl Renderer<'_> {
|
||||||
self.corner_radius = Some(inner_cr);
|
self.corner_radius = Some(inner_cr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !child.node.node_is_container() {
|
child
|
||||||
self.render_window_body_background(body);
|
.node
|
||||||
}
|
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
||||||
let body = self.base.scale_rect(body);
|
|
||||||
self.render_child_or_snapshot(
|
|
||||||
&child.node,
|
|
||||||
x + content.x1(),
|
|
||||||
y + content.y1(),
|
|
||||||
Some(&body),
|
|
||||||
);
|
|
||||||
self.stretch = None;
|
self.stretch = None;
|
||||||
self.corner_radius = None;
|
self.corner_radius = None;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -842,16 +578,10 @@ impl Renderer<'_> {
|
||||||
self.corner_radius = Some(inner_cr);
|
self.corner_radius = Some(inner_cr);
|
||||||
}
|
}
|
||||||
let body = body.move_(x, y);
|
let body = body.move_(x, y);
|
||||||
if !child.node.node_is_container() {
|
|
||||||
self.render_window_body_background(body);
|
|
||||||
}
|
|
||||||
let body = self.base.scale_rect(body);
|
let body = self.base.scale_rect(body);
|
||||||
self.render_child_or_snapshot(
|
child
|
||||||
&child.node,
|
.node
|
||||||
x + content.x1(),
|
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
||||||
y + content.y1(),
|
|
||||||
Some(&body),
|
|
||||||
);
|
|
||||||
self.stretch = None;
|
self.stretch = None;
|
||||||
self.corner_radius = None;
|
self.corner_radius = None;
|
||||||
}
|
}
|
||||||
|
|
@ -1085,10 +815,6 @@ impl Renderer<'_> {
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let pos = floating.position.get();
|
let pos = floating.position.get();
|
||||||
let visual =
|
|
||||||
self.state
|
|
||||||
.animations
|
|
||||||
.visual_rect(floating.node_id(), pos, self.state.now_nsec());
|
|
||||||
let theme = &self.state.theme;
|
let theme = &self.state.theme;
|
||||||
let bw = theme.sizes.border_width.get();
|
let bw = theme.sizes.border_width.get();
|
||||||
let bc = if floating.active.get() {
|
let bc = if floating.active.get() {
|
||||||
|
|
@ -1097,27 +823,16 @@ impl Renderer<'_> {
|
||||||
theme.colors.border.get()
|
theme.colors.border.get()
|
||||||
};
|
};
|
||||||
let cr = theme.corner_radius.get();
|
let cr = theme.corner_radius.get();
|
||||||
let outer = Rect::new_sized_saturating(0, 0, visual.width(), visual.height());
|
let outer = Rect::new_sized_saturating(0, 0, pos.width(), pos.height());
|
||||||
self.render_rounded_frame(outer, &bc, cr, bw, x, y);
|
self.render_rounded_frame(outer, &bc, cr, bw, x, y);
|
||||||
let body = Rect::new_sized_saturating(
|
let body =
|
||||||
x + bw,
|
Rect::new_sized_saturating(x + bw, y + bw, pos.width() - 2 * bw, pos.height() - 2 * bw);
|
||||||
y + bw,
|
|
||||||
visual.width() - 2 * bw,
|
|
||||||
visual.height() - 2 * bw,
|
|
||||||
);
|
|
||||||
let scissor_body = self.base.scale_rect(body);
|
let scissor_body = self.base.scale_rect(body);
|
||||||
self.stretch = if pos.width() != visual.width() || pos.height() != visual.height() {
|
|
||||||
Some(self.base.scale_point(body.width(), body.height()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if !cr.is_zero() {
|
if !cr.is_zero() {
|
||||||
let inner_cr = self.scale_corner_radius(cr.expanded_by(-(bw as f32)));
|
let inner_cr = self.scale_corner_radius(cr.expanded_by(-(bw as f32)));
|
||||||
self.corner_radius = Some(inner_cr);
|
self.corner_radius = Some(inner_cr);
|
||||||
}
|
}
|
||||||
self.render_window_body_background(body);
|
child.node_render(self, body.x1(), body.y1(), Some(&scissor_body));
|
||||||
self.render_child_or_snapshot(&child, body.x1(), body.y1(), Some(&scissor_body));
|
|
||||||
self.stretch = None;
|
|
||||||
self.corner_radius = None;
|
self.corner_radius = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
997
src/state.rs
997
src/state.rs
File diff suppressed because it is too large
Load diff
|
|
@ -32,7 +32,6 @@ use {
|
||||||
numcell::NumCell,
|
numcell::NumCell,
|
||||||
on_drop_event::OnDropEvent,
|
on_drop_event::OnDropEvent,
|
||||||
rc_eq::rc_eq,
|
rc_eq::rc_eq,
|
||||||
scroller::Scroller,
|
|
||||||
threshold_counter::ThresholdCounter,
|
threshold_counter::ThresholdCounter,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -133,7 +132,6 @@ pub struct ContainerNode {
|
||||||
pub sum_factors: Cell<f64>,
|
pub sum_factors: Cell<f64>,
|
||||||
pub layout_scheduled: Cell<bool>,
|
pub layout_scheduled: Cell<bool>,
|
||||||
animate_next_layout: Cell<bool>,
|
animate_next_layout: Cell<bool>,
|
||||||
pub mono_transition_animation_pending: Cell<bool>,
|
|
||||||
compute_render_positions_scheduled: Cell<bool>,
|
compute_render_positions_scheduled: Cell<bool>,
|
||||||
num_children: NumCell<usize>,
|
num_children: NumCell<usize>,
|
||||||
pub children: LinkedList<ContainerChild>,
|
pub children: LinkedList<ContainerChild>,
|
||||||
|
|
@ -151,7 +149,6 @@ pub struct ContainerNode {
|
||||||
pub child_removed: Rc<LazyEventSource>,
|
pub child_removed: Rc<LazyEventSource>,
|
||||||
pub all_children_resized: Rc<LazyEventSource>,
|
pub all_children_resized: Rc<LazyEventSource>,
|
||||||
pub tab_bar: RefCell<Option<TabBar>>,
|
pub tab_bar: RefCell<Option<TabBar>>,
|
||||||
scroll: Scroller,
|
|
||||||
pub update_tab_textures_scheduled: Cell<bool>,
|
pub update_tab_textures_scheduled: Cell<bool>,
|
||||||
pub ephemeral: Cell<Ephemeral>,
|
pub ephemeral: Cell<Ephemeral>,
|
||||||
}
|
}
|
||||||
|
|
@ -243,7 +240,6 @@ impl ContainerNode {
|
||||||
sum_factors: Cell::new(1.0),
|
sum_factors: Cell::new(1.0),
|
||||||
layout_scheduled: Cell::new(false),
|
layout_scheduled: Cell::new(false),
|
||||||
animate_next_layout: Cell::new(false),
|
animate_next_layout: Cell::new(false),
|
||||||
mono_transition_animation_pending: Cell::new(false),
|
|
||||||
compute_render_positions_scheduled: Cell::new(false),
|
compute_render_positions_scheduled: Cell::new(false),
|
||||||
num_children: NumCell::new(1),
|
num_children: NumCell::new(1),
|
||||||
children,
|
children,
|
||||||
|
|
@ -268,7 +264,6 @@ impl ContainerNode {
|
||||||
child_removed: state.lazy_event_sources.create_source(),
|
child_removed: state.lazy_event_sources.create_source(),
|
||||||
all_children_resized: state.post_layout_event_sources.create_source(),
|
all_children_resized: state.post_layout_event_sources.create_source(),
|
||||||
tab_bar: RefCell::new(None),
|
tab_bar: RefCell::new(None),
|
||||||
scroll: Default::default(),
|
|
||||||
update_tab_textures_scheduled: Cell::new(false),
|
update_tab_textures_scheduled: Cell::new(false),
|
||||||
ephemeral: Cell::new(Ephemeral::Off),
|
ephemeral: Cell::new(Ephemeral::Off),
|
||||||
});
|
});
|
||||||
|
|
@ -293,47 +288,6 @@ impl ContainerNode {
|
||||||
self.add_child_x(prev, new, |prev, new| self.add_child_after_(prev, new));
|
self.add_child_x(prev, new, |prev, new| self.add_child_after_(prev, new));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_tiled_child_after(self: &Rc<Self>, prev: &dyn Node, new: Rc<dyn ToplevelNode>) {
|
|
||||||
if !self.state.theme.autotile_enabled.get()
|
|
||||||
|| self.mono_child.is_some()
|
|
||||||
|| self.num_children.get() <= 1
|
|
||||||
{
|
|
||||||
self.add_child_after(prev, new);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let focused = self
|
|
||||||
.child_nodes
|
|
||||||
.borrow()
|
|
||||||
.get(&prev.node_id())
|
|
||||||
.map(|n| n.to_ref());
|
|
||||||
let Some(focused) = focused else {
|
|
||||||
log::error!(
|
|
||||||
"Tried to autotile a child into a container but the preceding node is not in the container"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let focused_node = focused.node.clone();
|
|
||||||
let focused_active = focused_node.tl_data().active();
|
|
||||||
let sub = ContainerNode::new(
|
|
||||||
&self.state,
|
|
||||||
&self.workspace.get(),
|
|
||||||
focused_node.clone(),
|
|
||||||
self.split.get().other(),
|
|
||||||
);
|
|
||||||
// Autotile-created groups are structural and collapse once only one
|
|
||||||
// child remains. Explicit make-group commands control their own
|
|
||||||
// grouping through the regular manual paths.
|
|
||||||
sub.ephemeral.set(Ephemeral::On);
|
|
||||||
sub.append_child(new);
|
|
||||||
let sub_id = sub.node_id();
|
|
||||||
self.clone().cnode_replace_child(&*focused_node, sub);
|
|
||||||
if focused_active
|
|
||||||
&& let Some(group) = self.child_nodes.borrow().get(&sub_id).map(|n| n.to_ref())
|
|
||||||
{
|
|
||||||
self.update_child_active(&group, true, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_child_before(self: &Rc<Self>, prev: &dyn Node, new: Rc<dyn ToplevelNode>) {
|
pub fn add_child_before(self: &Rc<Self>, prev: &dyn Node, new: Rc<dyn ToplevelNode>) {
|
||||||
self.add_child_x(prev, new, |prev, new| self.add_child_before_(prev, new));
|
self.add_child_x(prev, new, |prev, new| self.add_child_before_(prev, new));
|
||||||
}
|
}
|
||||||
|
|
@ -519,7 +473,6 @@ impl ContainerNode {
|
||||||
fn perform_layout(self: &Rc<Self>) {
|
fn perform_layout(self: &Rc<Self>) {
|
||||||
self.layout_scheduled.set(false);
|
self.layout_scheduled.set(false);
|
||||||
if self.num_children.get() == 0 {
|
if self.num_children.get() == 0 {
|
||||||
self.mono_transition_animation_pending.set(false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(child) = self.mono_child.get() {
|
if let Some(child) = self.mono_child.get() {
|
||||||
|
|
@ -537,7 +490,6 @@ impl ContainerNode {
|
||||||
self.damage();
|
self.damage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.mono_transition_animation_pending.set(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_mono_layout(self: &Rc<Self>, child: &ContainerChild) {
|
fn perform_mono_layout(self: &Rc<Self>, child: &ContainerChild) {
|
||||||
|
|
@ -796,18 +748,6 @@ impl ContainerNode {
|
||||||
self.activate_child2(child, false);
|
self.activate_child2(child, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate_child_from_input(
|
|
||||||
self: &Rc<Self>,
|
|
||||||
child: &NodeRef<ContainerChild>,
|
|
||||||
seat: &Rc<WlSeatGlobal>,
|
|
||||||
) {
|
|
||||||
self.activate_child(child);
|
|
||||||
child
|
|
||||||
.node
|
|
||||||
.clone()
|
|
||||||
.node_do_focus(seat, Direction::Unspecified);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn activate_child2(self: &Rc<Self>, child: &NodeRef<ContainerChild>, preserve_focus: bool) {
|
fn activate_child2(self: &Rc<Self>, child: &NodeRef<ContainerChild>, preserve_focus: bool) {
|
||||||
if let Some(mc) = self.mono_child.get() {
|
if let Some(mc) = self.mono_child.get() {
|
||||||
if mc.node.node_id() == child.node.node_id() {
|
if mc.node.node_id() == child.node.node_id() {
|
||||||
|
|
@ -883,7 +823,6 @@ impl ContainerNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.mono_child.set(child.clone());
|
self.mono_child.set(child.clone());
|
||||||
self.mono_transition_animation_pending.set(true);
|
|
||||||
if child.is_some() {
|
if child.is_some() {
|
||||||
self.rebuild_tab_bar();
|
self.rebuild_tab_bar();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1425,6 +1364,42 @@ impl ContainerNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_child(self: &Rc<Self>, node: Rc<dyn ToplevelNode>, direction: Direction) {
|
pub fn insert_child(self: &Rc<Self>, node: Rc<dyn ToplevelNode>, direction: Direction) {
|
||||||
|
// Autotile: if the container would become too narrow/tall, wrap the
|
||||||
|
// focused child and new node in a perpendicular sub-container.
|
||||||
|
if self.state.theme.autotile_enabled.get() && self.mono_child.is_none() {
|
||||||
|
let (pw, ph) = self.predict_child_body_size();
|
||||||
|
let opposite = match self.split.get() {
|
||||||
|
ContainerSplit::Horizontal if pw > 0 && ph > 0 && pw < ph => {
|
||||||
|
Some(ContainerSplit::Vertical)
|
||||||
|
}
|
||||||
|
ContainerSplit::Vertical if pw > 0 && ph > 0 && ph < pw => {
|
||||||
|
Some(ContainerSplit::Horizontal)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(opp_split) = opposite {
|
||||||
|
if let Some(focused) = self.focus_history.last() {
|
||||||
|
if self.num_children.get() <= 1 {
|
||||||
|
// Single child, autotile not applicable.
|
||||||
|
} else {
|
||||||
|
let focused_node = focused.node.clone();
|
||||||
|
let was_ephemeral = self.ephemeral.replace(Ephemeral::Off);
|
||||||
|
self.clone().cnode_remove_child2(&*focused_node, true);
|
||||||
|
self.ephemeral.set(was_ephemeral);
|
||||||
|
let sub = ContainerNode::new(
|
||||||
|
&self.state,
|
||||||
|
&self.workspace.get(),
|
||||||
|
focused_node,
|
||||||
|
opp_split,
|
||||||
|
);
|
||||||
|
sub.ephemeral.set(Ephemeral::On);
|
||||||
|
sub.append_child(node);
|
||||||
|
self.append_child(sub);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let (split, right) = direction_to_split(direction);
|
let (split, right) = direction_to_split(direction);
|
||||||
if split != self.split.get() || right {
|
if split != self.split.get() || right {
|
||||||
self.append_child(node);
|
self.append_child(node);
|
||||||
|
|
@ -1534,7 +1509,7 @@ impl ContainerNode {
|
||||||
fn button(
|
fn button(
|
||||||
self: Rc<Self>,
|
self: Rc<Self>,
|
||||||
id: CursorType,
|
id: CursorType,
|
||||||
seat: &Rc<WlSeatGlobal>,
|
_seat: &Rc<WlSeatGlobal>,
|
||||||
_time_usec: u64,
|
_time_usec: u64,
|
||||||
pressed: bool,
|
pressed: bool,
|
||||||
button: u32,
|
button: u32,
|
||||||
|
|
@ -1564,7 +1539,7 @@ impl ContainerNode {
|
||||||
if let Some(child) = children.get(&child_id) {
|
if let Some(child) = children.get(&child_id) {
|
||||||
let child_ref = child.to_ref();
|
let child_ref = child.to_ref();
|
||||||
drop(children);
|
drop(children);
|
||||||
self.activate_child_from_input(&child_ref, seat);
|
self.activate_child(&child_ref);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1791,39 +1766,12 @@ enum SeatOpKind {
|
||||||
|
|
||||||
pub async fn container_layout(state: Rc<State>) {
|
pub async fn container_layout(state: Rc<State>) {
|
||||||
loop {
|
loop {
|
||||||
let first = state.pending_container_layout.pop().await;
|
let container = state.pending_container_layout.pop().await;
|
||||||
let mut containers = vec![first];
|
if container.layout_scheduled.get() {
|
||||||
while let Some(container) = state.pending_container_layout.try_pop() {
|
|
||||||
containers.push(container);
|
|
||||||
}
|
|
||||||
let mut animated = vec![];
|
|
||||||
let mut immediate = vec![];
|
|
||||||
for container in containers {
|
|
||||||
if !container.layout_scheduled.get() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let animate = container.animate_next_layout.replace(false)
|
let animate = container.animate_next_layout.replace(false)
|
||||||
&& !state.suppress_animations_for_next_layout.get();
|
&& !state.suppress_animations_for_next_layout.get();
|
||||||
if animate {
|
let prev_active = state.layout_animations_active.replace(animate);
|
||||||
animated.push(container);
|
container.perform_layout();
|
||||||
} else {
|
|
||||||
immediate.push(container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !animated.is_empty() {
|
|
||||||
let prev_active = state.layout_animations_active.replace(true);
|
|
||||||
state.begin_layout_animation_batch();
|
|
||||||
for container in animated {
|
|
||||||
container.perform_layout();
|
|
||||||
}
|
|
||||||
state.finish_layout_animation_batch();
|
|
||||||
state.layout_animations_active.set(prev_active);
|
|
||||||
}
|
|
||||||
if !immediate.is_empty() {
|
|
||||||
let prev_active = state.layout_animations_active.replace(false);
|
|
||||||
for container in immediate {
|
|
||||||
container.perform_layout();
|
|
||||||
}
|
|
||||||
state.layout_animations_active.set(prev_active);
|
state.layout_animations_active.set(prev_active);
|
||||||
}
|
}
|
||||||
state.suppress_animations_for_next_layout.set(false);
|
state.suppress_animations_for_next_layout.set(false);
|
||||||
|
|
@ -2081,33 +2029,31 @@ impl Node for ContainerNode {
|
||||||
self.button(id, seat, time_usec, state == ButtonState::Pressed, button);
|
self.button(id, seat, time_usec, state == ButtonState::Pressed, button);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_on_axis_event(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, event: &PendingScroll) {
|
fn node_on_axis_event(self: Rc<Self>, _seat: &Rc<WlSeatGlobal>, event: &PendingScroll) {
|
||||||
if self.mono_child.is_none() {
|
if self.mono_child.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let steps = match self.scroll.handle(event) {
|
// Use vertical scroll (index 1) to switch tabs.
|
||||||
Some(steps) => steps,
|
let v = match event.v120[1].get() {
|
||||||
|
Some(v) if v != 0 => v,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let mut target = match self.mono_child.get() {
|
let mono = match self.mono_child.get() {
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
let current_id = target.node.node_id();
|
let next = if v > 0 {
|
||||||
for _ in 0..steps.abs() {
|
// Scroll down → next tab.
|
||||||
let next = if steps > 0 {
|
mono.next().or_else(|| self.children.first())
|
||||||
target.next().or_else(|| self.children.first())
|
} else {
|
||||||
} else {
|
// Scroll up → previous tab.
|
||||||
target.prev().or_else(|| self.children.last())
|
mono.prev().or_else(|| self.children.last())
|
||||||
};
|
};
|
||||||
match next {
|
if let Some(next) = next {
|
||||||
Some(next) => target = next,
|
if next.node.node_id() != mono.node.node_id() {
|
||||||
None => break,
|
self.activate_child(&next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if target.node.node_id() != current_id {
|
|
||||||
self.activate_child_from_input(&target, seat);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_on_leave(&self, seat: &WlSeatGlobal) {
|
fn node_on_leave(&self, seat: &WlSeatGlobal) {
|
||||||
|
|
@ -2325,11 +2271,6 @@ impl ContainingNode for ContainerNode {
|
||||||
}
|
}
|
||||||
// log::info!("cnode_remove_child2");
|
// log::info!("cnode_remove_child2");
|
||||||
self.rebuild_tab_bar();
|
self.rebuild_tab_bar();
|
||||||
if self.state.animations.enabled.get()
|
|
||||||
&& !self.state.suppress_animations_for_next_layout.get()
|
|
||||||
{
|
|
||||||
self.animate_next_layout.set(true);
|
|
||||||
}
|
|
||||||
self.schedule_layout();
|
self.schedule_layout();
|
||||||
self.cancel_seat_ops();
|
self.cancel_seat_ops();
|
||||||
self.child_removed.trigger();
|
self.child_removed.trigger();
|
||||||
|
|
|
||||||
|
|
@ -8,25 +8,18 @@ use {
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
state::State,
|
state::State,
|
||||||
tree::{
|
tree::{
|
||||||
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink,
|
FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation,
|
||||||
NodeLocation, OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination,
|
OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination,
|
||||||
WorkspaceNodeId, walker::NodeVisitor,
|
WorkspaceNodeId, walker::NodeVisitor,
|
||||||
},
|
},
|
||||||
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
|
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
|
||||||
},
|
},
|
||||||
std::{
|
std::{cell::Cell, ops::Deref, rc::Rc},
|
||||||
cell::{Cell, RefCell},
|
|
||||||
mem,
|
|
||||||
ops::Deref,
|
|
||||||
rc::{Rc, Weak},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct DisplayNode {
|
pub struct DisplayNode {
|
||||||
pub id: NodeId,
|
pub id: NodeId,
|
||||||
pub extents: Cell<Rect>,
|
pub extents: Cell<Rect>,
|
||||||
visible: Cell<bool>,
|
|
||||||
suspend_restore_kb_foci: RefCell<Vec<(Rc<WlSeatGlobal>, Weak<dyn Node>)>>,
|
|
||||||
pub outputs: CopyHashMap<ConnectorId, Rc<OutputNode>>,
|
pub outputs: CopyHashMap<ConnectorId, Rc<OutputNode>>,
|
||||||
pub stacked: Rc<LinkedList<Rc<dyn StackedNode>>>,
|
pub stacked: Rc<LinkedList<Rc<dyn StackedNode>>>,
|
||||||
pub stacked_above_layers: Rc<LinkedList<Rc<dyn StackedNode>>>,
|
pub stacked_above_layers: Rc<LinkedList<Rc<dyn StackedNode>>>,
|
||||||
|
|
@ -38,8 +31,6 @@ impl DisplayNode {
|
||||||
let slf = Self {
|
let slf = Self {
|
||||||
id,
|
id,
|
||||||
extents: Default::default(),
|
extents: Default::default(),
|
||||||
visible: Default::default(),
|
|
||||||
suspend_restore_kb_foci: Default::default(),
|
|
||||||
outputs: Default::default(),
|
outputs: Default::default(),
|
||||||
stacked: Default::default(),
|
stacked: Default::default(),
|
||||||
stacked_above_layers: Default::default(),
|
stacked_above_layers: Default::default(),
|
||||||
|
|
@ -80,17 +71,6 @@ impl DisplayNode {
|
||||||
|
|
||||||
pub fn update_visible(&self, state: &State) {
|
pub fn update_visible(&self, state: &State) {
|
||||||
let visible = state.root_visible();
|
let visible = state.root_visible();
|
||||||
let was_visible = self.visible.replace(visible);
|
|
||||||
if !visible && was_visible {
|
|
||||||
let mut foci = self.suspend_restore_kb_foci.borrow_mut();
|
|
||||||
foci.clear();
|
|
||||||
for seat in state.globals.seats.lock().values() {
|
|
||||||
let node = seat.get_keyboard_node();
|
|
||||||
if node.node_id() != self.id {
|
|
||||||
foci.push((seat.clone(), Rc::downgrade(&node)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for output in self.outputs.lock().values() {
|
for output in self.outputs.lock().values() {
|
||||||
output.update_visible();
|
output.update_visible();
|
||||||
}
|
}
|
||||||
|
|
@ -102,20 +82,6 @@ impl DisplayNode {
|
||||||
for seat in state.globals.seats.lock().values() {
|
for seat in state.globals.seats.lock().values() {
|
||||||
seat.set_visible(visible);
|
seat.set_visible(visible);
|
||||||
}
|
}
|
||||||
if visible && !was_visible {
|
|
||||||
for (seat, node) in mem::take(&mut *self.suspend_restore_kb_foci.borrow_mut()) {
|
|
||||||
if seat.get_keyboard_node().node_id() == self.id {
|
|
||||||
if let Some(node) = node.upgrade()
|
|
||||||
&& node.node_visible()
|
|
||||||
{
|
|
||||||
seat.focus_node(node);
|
|
||||||
} else {
|
|
||||||
seat.get_fallback_output()
|
|
||||||
.take_keyboard_navigation_focus(&seat, Direction::Unspecified);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if visible {
|
if visible {
|
||||||
state.damage(self.extents.get());
|
state.damage(self.extents.get());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,6 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
tree_id!(FloatNodeId);
|
tree_id!(FloatNodeId);
|
||||||
|
|
||||||
const COMMAND_MOVE_DELTA: i32 = 100;
|
|
||||||
|
|
||||||
pub struct FloatNode {
|
pub struct FloatNode {
|
||||||
pub id: FloatNodeId,
|
pub id: FloatNodeId,
|
||||||
pub state: Rc<State>,
|
pub state: Rc<State>,
|
||||||
|
|
@ -156,13 +153,6 @@ impl FloatNode {
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let pos = self.position.get();
|
let pos = self.position.get();
|
||||||
let spawn_in_pending = {
|
|
||||||
let data = child.tl_data();
|
|
||||||
data.spawn_in_pending.get() && data.kind.is_app_window() && !data.is_fullscreen.get()
|
|
||||||
};
|
|
||||||
if spawn_in_pending && self.visible.get() {
|
|
||||||
self.state.queue_spawn_in_animation(self.id.into(), pos);
|
|
||||||
}
|
|
||||||
let theme = &self.state.theme;
|
let theme = &self.state.theme;
|
||||||
let bw = theme.sizes.border_width.get();
|
let bw = theme.sizes.border_width.get();
|
||||||
let cpos = Rect::new_sized_saturating(
|
let cpos = Rect::new_sized_saturating(
|
||||||
|
|
@ -373,50 +363,6 @@ impl FloatNode {
|
||||||
y2 += y1 - pos.y1();
|
y2 += y1 - pos.y1();
|
||||||
}
|
}
|
||||||
let new_pos = Rect::new_saturating(x1, y1, x2, y2);
|
let new_pos = Rect::new_saturating(x1, y1, x2, y2);
|
||||||
self.set_position(new_pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_by_direction(self: &Rc<Self>, direction: Direction) {
|
|
||||||
let (dx, dy) = match direction {
|
|
||||||
Direction::Left => (-COMMAND_MOVE_DELTA, 0),
|
|
||||||
Direction::Down => (0, COMMAND_MOVE_DELTA),
|
|
||||||
Direction::Up => (0, -COMMAND_MOVE_DELTA),
|
|
||||||
Direction::Right => (COMMAND_MOVE_DELTA, 0),
|
|
||||||
Direction::Unspecified => return,
|
|
||||||
};
|
|
||||||
self.set_position(self.position.get().move_(dx, dy));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn body_for_outer(&self, outer: Rect) -> Rect {
|
|
||||||
let bw = self.state.theme.sizes.border_width.get();
|
|
||||||
Rect::new_sized_saturating(
|
|
||||||
outer.x1() + bw,
|
|
||||||
outer.y1() + bw,
|
|
||||||
outer.width() - 2 * bw,
|
|
||||||
outer.height() - 2 * bw,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn queue_position_animation(&self, old_pos: Rect, new_pos: Rect) {
|
|
||||||
self.state
|
|
||||||
.clone()
|
|
||||||
.queue_tiled_animation(self.id.into(), old_pos, new_pos);
|
|
||||||
let Some(child) = self.child.get() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
self.state.clone().queue_tiled_animation(
|
|
||||||
child.node_id(),
|
|
||||||
self.body_for_outer(old_pos),
|
|
||||||
self.body_for_outer(new_pos),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_position(self: &Rc<Self>, new_pos: Rect) {
|
|
||||||
let pos = self.position.get();
|
|
||||||
if new_pos == pos {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.queue_position_animation(pos, new_pos);
|
|
||||||
self.position.set(new_pos);
|
self.position.set(new_pos);
|
||||||
if self.visible.get() {
|
if self.visible.get() {
|
||||||
self.state.damage(pos);
|
self.state.damage(pos);
|
||||||
|
|
@ -845,7 +791,13 @@ impl ContainingNode for FloatNode {
|
||||||
let bw = theme.sizes.border_width.get();
|
let bw = theme.sizes.border_width.get();
|
||||||
let (x, y) = (x - bw, y - bw);
|
let (x, y) = (x - bw, y - bw);
|
||||||
let pos = self.position.get();
|
let pos = self.position.get();
|
||||||
self.set_position(pos.at_point(x, y));
|
if pos.position() != (x, y) {
|
||||||
|
let new_pos = pos.at_point(x, y);
|
||||||
|
self.position.set(new_pos);
|
||||||
|
self.state.damage(pos);
|
||||||
|
self.state.damage(new_pos);
|
||||||
|
self.schedule_layout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cnode_resize_child(
|
fn cnode_resize_child(
|
||||||
|
|
@ -876,7 +828,14 @@ impl ContainingNode for FloatNode {
|
||||||
y2 = (v + bw).max(y1 + bw + bw);
|
y2 = (v + bw).max(y1 + bw + bw);
|
||||||
}
|
}
|
||||||
let new_pos = Rect::new_saturating(x1, y1, x2, y2);
|
let new_pos = Rect::new_saturating(x1, y1, x2, y2);
|
||||||
self.set_position(new_pos);
|
if new_pos != pos {
|
||||||
|
self.position.set(new_pos);
|
||||||
|
if self.visible.get() {
|
||||||
|
self.state.damage(pos);
|
||||||
|
self.state.damage(new_pos);
|
||||||
|
}
|
||||||
|
self.schedule_layout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cnode_pinned(&self) -> bool {
|
fn cnode_pinned(&self) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,5 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
animation::{
|
|
||||||
RetainedExitLayer, RetainedToplevel,
|
|
||||||
multiphase::{
|
|
||||||
MultiphaseHierarchyPosition, MultiphaseHierarchyTransition,
|
|
||||||
MultiphaseWindowHierarchy, PhaseAxis,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
client::{Client, ClientId},
|
client::{Client, ClientId},
|
||||||
criteria::{
|
criteria::{
|
||||||
CritDestroyListener, CritMatcherId,
|
CritDestroyListener, CritMatcherId,
|
||||||
|
|
@ -124,7 +117,6 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
||||||
let parent_was_none = data.parent.set(Some(parent.clone())).is_none();
|
let parent_was_none = data.parent.set(Some(parent.clone())).is_none();
|
||||||
if parent_was_none {
|
if parent_was_none {
|
||||||
data.mapped_during_iteration.set(data.state.eng.iteration());
|
data.mapped_during_iteration.set(data.state.eng.iteration());
|
||||||
data.spawn_in_pending.set(data.kind.is_app_window());
|
|
||||||
data.property_changed(TL_CHANGED_NEW);
|
data.property_changed(TL_CHANGED_NEW);
|
||||||
}
|
}
|
||||||
let was_floating = data.parent_is_float.get();
|
let was_floating = data.parent_is_float.get();
|
||||||
|
|
@ -192,56 +184,22 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
||||||
fn tl_change_extents(self: Rc<Self>, rect: &Rect) {
|
fn tl_change_extents(self: Rc<Self>, rect: &Rect) {
|
||||||
let data = self.tl_data();
|
let data = self.tl_data();
|
||||||
let prev = data.desired_extents.replace(*rect);
|
let prev = data.desired_extents.replace(*rect);
|
||||||
let target_hierarchy = self.tl_multiphase_hierarchy_position();
|
let parent_is_mono = data
|
||||||
let hierarchy = MultiphaseWindowHierarchy::new(
|
|
||||||
data.layout_animation_position.replace(target_hierarchy),
|
|
||||||
target_hierarchy,
|
|
||||||
);
|
|
||||||
let spawn_in_pending = data.spawn_in_pending.get();
|
|
||||||
let spawn_in_eligible = spawn_in_pending
|
|
||||||
&& !rect.is_empty()
|
|
||||||
&& data.visible.get()
|
|
||||||
&& !data.is_fullscreen.get()
|
|
||||||
&& data.kind.is_app_window()
|
|
||||||
&& !self.node_is_container();
|
|
||||||
let parent_container = data
|
|
||||||
.parent
|
.parent
|
||||||
.get()
|
.get()
|
||||||
.and_then(|parent| parent.node_into_container());
|
.and_then(|parent| parent.node_into_container())
|
||||||
let parent_is_mono = parent_container
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|container| container.mono_child.is_some());
|
.is_some_and(|container| container.mono_child.is_some());
|
||||||
let parent_mono_transition = parent_container
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|container| container.mono_transition_animation_pending.get());
|
|
||||||
let active_mono_boundary = matches!(
|
|
||||||
hierarchy.transition,
|
|
||||||
MultiphaseHierarchyTransition::EnteringMono
|
|
||||||
| MultiphaseHierarchyTransition::ExitingMono
|
|
||||||
) && parent_mono_transition
|
|
||||||
&& (hierarchy.source.mono_active || hierarchy.target.mono_active);
|
|
||||||
if prev != *rect
|
if prev != *rect
|
||||||
&& !prev.is_empty()
|
&& !prev.is_empty()
|
||||||
&& !rect.is_empty()
|
&& !rect.is_empty()
|
||||||
&& data.visible.get()
|
&& data.visible.get()
|
||||||
&& !data.parent_is_float.get()
|
&& !data.parent_is_float.get()
|
||||||
&& !self.node_is_container()
|
&& !self.node_is_container()
|
||||||
&& (!parent_is_mono || active_mono_boundary)
|
&& !parent_is_mono
|
||||||
{
|
{
|
||||||
data.state.clone().queue_tiled_animation_with_hierarchy(
|
|
||||||
data.node_id,
|
|
||||||
prev,
|
|
||||||
*rect,
|
|
||||||
hierarchy,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if spawn_in_eligible {
|
|
||||||
data.state
|
data.state
|
||||||
.clone()
|
.clone()
|
||||||
.queue_spawn_in_animation(data.node_id, *rect);
|
.queue_tiled_animation(data.node_id, prev, *rect);
|
||||||
}
|
|
||||||
if spawn_in_eligible {
|
|
||||||
data.spawn_in_pending.set(false);
|
|
||||||
}
|
}
|
||||||
if prev.size() != rect.size() {
|
if prev.size() != rect.size() {
|
||||||
for sc in data.jay_screencasts.lock().values() {
|
for sc in data.jay_screencasts.lock().values() {
|
||||||
|
|
@ -334,35 +292,6 @@ pub trait ToplevelNodeBase: Node {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tl_multiphase_hierarchy_position(&self) -> MultiphaseHierarchyPosition {
|
|
||||||
let data = self.tl_data();
|
|
||||||
let Some(parent) = data.parent.get() else {
|
|
||||||
return Default::default();
|
|
||||||
};
|
|
||||||
let mut position = MultiphaseHierarchyPosition {
|
|
||||||
parent: Some(parent.node_id()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
populate_multiphase_ancestor_splits(&mut position, Some(parent.clone()));
|
|
||||||
if let Some(container) = parent.node_into_container() {
|
|
||||||
position.split_axis = Some(match container.split.get() {
|
|
||||||
ContainerSplit::Horizontal => PhaseAxis::Horizontal,
|
|
||||||
ContainerSplit::Vertical => PhaseAxis::Vertical,
|
|
||||||
});
|
|
||||||
if let Some(mono) = container.mono_child.get() {
|
|
||||||
position.parent_is_mono = true;
|
|
||||||
position.mono_active = mono.node.node_id() == data.node_id;
|
|
||||||
}
|
|
||||||
for (idx, child) in container.children.iter().enumerate() {
|
|
||||||
if child.node.node_id() == data.node_id {
|
|
||||||
position.sibling_index = Some(idx.min(u16::MAX as usize) as u16);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
position
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tl_set_active(&self, active: bool) {
|
fn tl_set_active(&self, active: bool) {
|
||||||
let _ = active;
|
let _ = active;
|
||||||
}
|
}
|
||||||
|
|
@ -387,11 +316,6 @@ pub trait ToplevelNodeBase: Node {
|
||||||
fn tl_scanout_surface(&self) -> Option<Rc<WlSurface>> {
|
fn tl_scanout_surface(&self) -> Option<Rc<WlSurface>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tl_animation_snapshot(&self) -> Option<Rc<RetainedToplevel>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tl_restack_popups(&self) {
|
fn tl_restack_popups(&self) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
@ -432,31 +356,6 @@ pub trait ToplevelNodeBase: Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn populate_multiphase_ancestor_splits(
|
|
||||||
position: &mut MultiphaseHierarchyPosition,
|
|
||||||
mut parent: Option<Rc<dyn ContainingNode>>,
|
|
||||||
) {
|
|
||||||
let mut depth = 0u16;
|
|
||||||
while let Some(node) = parent {
|
|
||||||
let Some(toplevel) = node.clone().node_into_toplevel() else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
depth = depth.saturating_add(1);
|
|
||||||
if let Some(container) = node.node_into_container() {
|
|
||||||
match container.split.get() {
|
|
||||||
ContainerSplit::Horizontal => {
|
|
||||||
position.nearest_horizontal_split_depth.get_or_insert(depth);
|
|
||||||
}
|
|
||||||
ContainerSplit::Vertical => {
|
|
||||||
position.nearest_vertical_split_depth.get_or_insert(depth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parent = toplevel.tl_data().parent.get();
|
|
||||||
}
|
|
||||||
position.depth = depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FullscreenedData {
|
pub struct FullscreenedData {
|
||||||
pub placeholder: Rc<PlaceholderNode>,
|
pub placeholder: Rc<PlaceholderNode>,
|
||||||
pub workspace: Rc<WorkspaceNode>,
|
pub workspace: Rc<WorkspaceNode>,
|
||||||
|
|
@ -495,13 +394,6 @@ impl ToplevelType {
|
||||||
ToplevelType::XWindow { .. } => window::X_WINDOW,
|
ToplevelType::XWindow { .. } => window::X_WINDOW,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_app_window(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
ToplevelType::XdgToplevel(_) | ToplevelType::XWindow(_)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ToplevelData {
|
pub struct ToplevelData {
|
||||||
|
|
@ -524,10 +416,8 @@ pub struct ToplevelData {
|
||||||
pub title: RefCell<String>,
|
pub title: RefCell<String>,
|
||||||
pub parent: CloneCell<Option<Rc<dyn ContainingNode>>>,
|
pub parent: CloneCell<Option<Rc<dyn ContainingNode>>>,
|
||||||
pub mapped_during_iteration: Cell<u64>,
|
pub mapped_during_iteration: Cell<u64>,
|
||||||
pub spawn_in_pending: Cell<bool>,
|
|
||||||
pub pos: Cell<Rect>,
|
pub pos: Cell<Rect>,
|
||||||
pub desired_extents: Cell<Rect>,
|
pub desired_extents: Cell<Rect>,
|
||||||
pub layout_animation_position: Cell<MultiphaseHierarchyPosition>,
|
|
||||||
pub seat_state: NodeSeatState,
|
pub seat_state: NodeSeatState,
|
||||||
pub wants_attention: Cell<bool>,
|
pub wants_attention: Cell<bool>,
|
||||||
pub requested_attention: Cell<bool>,
|
pub requested_attention: Cell<bool>,
|
||||||
|
|
@ -589,10 +479,8 @@ impl ToplevelData {
|
||||||
title: RefCell::new(title),
|
title: RefCell::new(title),
|
||||||
parent: Default::default(),
|
parent: Default::default(),
|
||||||
mapped_during_iteration: Cell::new(0),
|
mapped_during_iteration: Cell::new(0),
|
||||||
spawn_in_pending: Cell::new(false),
|
|
||||||
pos: Default::default(),
|
pos: Default::default(),
|
||||||
desired_extents: Default::default(),
|
desired_extents: Default::default(),
|
||||||
layout_animation_position: Default::default(),
|
|
||||||
seat_state: Default::default(),
|
seat_state: Default::default(),
|
||||||
wants_attention: Cell::new(false),
|
wants_attention: Cell::new(false),
|
||||||
requested_attention: Cell::new(false),
|
requested_attention: Cell::new(false),
|
||||||
|
|
@ -979,7 +867,7 @@ impl ToplevelData {
|
||||||
}
|
}
|
||||||
fd.workspace.remove_fullscreen_node();
|
fd.workspace.remove_fullscreen_node();
|
||||||
if fd.placeholder.is_destroyed() {
|
if fd.placeholder.is_destroyed() {
|
||||||
state.map_tiled_without_autotile(node);
|
state.map_tiled(node);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let parent = fd.placeholder.tl_data().parent.take().unwrap();
|
let parent = fd.placeholder.tl_data().parent.take().unwrap();
|
||||||
|
|
@ -1064,62 +952,6 @@ impl ToplevelData {
|
||||||
self.mapped_during_iteration.get() == self.state.eng.iteration()
|
self.mapped_during_iteration.get() == self.state.eng.iteration()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_spawn_out(&self, node: &dyn ToplevelNode, retained: Option<Rc<RetainedToplevel>>) {
|
|
||||||
if !self.kind.is_app_window()
|
|
||||||
|| !self.visible.get()
|
|
||||||
|| self.is_fullscreen.get()
|
|
||||||
|| node.node_is_container()
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Some(retained) = retained else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let bw = self.state.theme.sizes.border_width.get().max(0);
|
|
||||||
let now = self.state.now_nsec();
|
|
||||||
let (outer, frame_inset, layer) = if self.parent_is_float.get() {
|
|
||||||
let Some(float) = self.float.get() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
(
|
|
||||||
self.state
|
|
||||||
.animations
|
|
||||||
.visual_rect(float.node_id(), float.position.get(), now),
|
|
||||||
bw,
|
|
||||||
RetainedExitLayer::Floating,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let body =
|
|
||||||
self.state
|
|
||||||
.animations
|
|
||||||
.visual_rect(self.node_id, node.node_absolute_position(), now);
|
|
||||||
if body.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if self.state.theme.sizes.gap.get() != 0 {
|
|
||||||
(
|
|
||||||
Rect::new_sized_saturating(
|
|
||||||
body.x1() - bw,
|
|
||||||
body.y1() - bw,
|
|
||||||
body.width() + 2 * bw,
|
|
||||||
body.height() + 2 * bw,
|
|
||||||
),
|
|
||||||
bw,
|
|
||||||
RetainedExitLayer::Tiled,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(body, 0, RetainedExitLayer::Tiled)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.state.clone().queue_spawn_out_animation(
|
|
||||||
outer,
|
|
||||||
frame_inset,
|
|
||||||
retained,
|
|
||||||
self.active(),
|
|
||||||
layer,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_content_type(&self, content_type: Option<ContentType>) {
|
pub fn set_content_type(&self, content_type: Option<ContentType>) {
|
||||||
if self.content_type.replace(content_type) != content_type {
|
if self.content_type.replace(content_type) != content_type {
|
||||||
self.property_changed(TL_CHANGED_CONTENT_TY);
|
self.property_changed(TL_CHANGED_CONTENT_TY);
|
||||||
|
|
@ -1228,26 +1060,6 @@ pub fn toplevel_create_split(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, axis:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn float_outer_for_body(state: &State, body: Rect) -> Rect {
|
|
||||||
let bw = state.theme.sizes.border_width.get();
|
|
||||||
Rect::new_sized_saturating(
|
|
||||||
body.x1() - bw,
|
|
||||||
body.y1() - bw,
|
|
||||||
body.width() + 2 * bw,
|
|
||||||
body.height() + 2 * bw,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn float_body_for_outer(state: &State, outer: Rect) -> Rect {
|
|
||||||
let bw = state.theme.sizes.border_width.get();
|
|
||||||
Rect::new_sized_saturating(
|
|
||||||
outer.x1() + bw,
|
|
||||||
outer.y1() + bw,
|
|
||||||
outer.width() - 2 * bw,
|
|
||||||
outer.height() - 2 * bw,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floating: bool) {
|
pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floating: bool) {
|
||||||
let data = tl.tl_data();
|
let data = tl.tl_data();
|
||||||
if data.is_fullscreen.get() {
|
if data.is_fullscreen.get() {
|
||||||
|
|
@ -1262,21 +1074,11 @@ pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floati
|
||||||
};
|
};
|
||||||
if !floating {
|
if !floating {
|
||||||
parent.cnode_remove_child2(&*tl, true);
|
parent.cnode_remove_child2(&*tl, true);
|
||||||
state.map_tiled_without_autotile(tl);
|
state.map_tiled(tl);
|
||||||
} else if let Some(ws) = data.workspace.get() {
|
} else if let Some(ws) = data.workspace.get() {
|
||||||
let node_id = data.node_id;
|
|
||||||
let old_body =
|
|
||||||
state
|
|
||||||
.animations
|
|
||||||
.visual_rect(node_id, tl.node_absolute_position(), state.now_nsec());
|
|
||||||
let old_outer = float_outer_for_body(state, old_body);
|
|
||||||
parent.cnode_remove_child2(&*tl, true);
|
parent.cnode_remove_child2(&*tl, true);
|
||||||
let (width, height) = data.float_size(&ws);
|
let (width, height) = data.float_size(&ws);
|
||||||
let floater = state.map_floating(tl, width, height, &ws, None);
|
state.map_floating(tl, width, height, &ws, None);
|
||||||
let new_outer = floater.position.get();
|
|
||||||
let new_body = float_body_for_outer(state, new_outer);
|
|
||||||
state.queue_linear_layout_animation(floater.node_id(), old_outer, new_outer);
|
|
||||||
state.queue_linear_layout_animation(node_id, old_body, new_body);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1323,54 +1125,3 @@ pub fn toplevel_set_workspace(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, ws: &
|
||||||
tl.tl_set_fullscreen(true, Some(ws.clone()));
|
tl.tl_set_fullscreen(true, Some(ws.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a toplevel from the tree so it can be parked in a scratchpad.
|
|
||||||
///
|
|
||||||
/// Returns `true` if the window was hidden. A placeholder, a window without a
|
|
||||||
/// parent, or a window that refuses to leave fullscreen cannot be parked.
|
|
||||||
pub fn toplevel_hide_for_scratchpad(tl: Rc<dyn ToplevelNode>) -> bool {
|
|
||||||
if tl.node_is_placeholder() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let data = tl.tl_data();
|
|
||||||
let workspace = data.workspace.get();
|
|
||||||
if data.is_fullscreen.get() {
|
|
||||||
tl.clone().tl_set_fullscreen(false, None);
|
|
||||||
if data.is_fullscreen.get() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let Some(parent) = data.parent.get() else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let kb_foci = collect_kb_foci(tl.clone());
|
|
||||||
parent.cnode_remove_child2(&*tl, true);
|
|
||||||
data.parent.take();
|
|
||||||
data.float.take();
|
|
||||||
if data.parent_is_float.replace(false) {
|
|
||||||
data.property_changed(TL_CHANGED_FLOATING);
|
|
||||||
}
|
|
||||||
if data.workspace.take().is_some() {
|
|
||||||
data.property_changed(TL_CHANGED_WORKSPACE);
|
|
||||||
}
|
|
||||||
tl.tl_set_visible(false);
|
|
||||||
if let Some(workspace) = &workspace {
|
|
||||||
for seat in kb_foci {
|
|
||||||
workspace
|
|
||||||
.clone()
|
|
||||||
.node_do_focus(&seat, Direction::Unspecified);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps a parked scratchpad window back onto `ws`. Scratchpad windows always
|
|
||||||
/// return floating, regardless of how they were laid out before parking.
|
|
||||||
pub fn toplevel_restore_from_scratchpad(
|
|
||||||
state: &Rc<State>,
|
|
||||||
tl: Rc<dyn ToplevelNode>,
|
|
||||||
ws: &Rc<WorkspaceNode>,
|
|
||||||
) {
|
|
||||||
let (width, height) = tl.tl_data().float_size(ws);
|
|
||||||
state.map_floating(tl.clone(), width, height, ws, None);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -197,10 +197,10 @@ impl WorkspaceNode {
|
||||||
}
|
}
|
||||||
self.pull_child_properties(&**container);
|
self.pull_child_properties(&**container);
|
||||||
let pos = self.position.get();
|
let pos = self.position.get();
|
||||||
|
container.clone().tl_change_extents(&pos);
|
||||||
container.tl_set_parent(self.clone());
|
container.tl_set_parent(self.clone());
|
||||||
container.tl_set_visible(self.container_visible());
|
container.tl_set_visible(self.container_visible());
|
||||||
self.container.set(Some(container.clone()));
|
self.container.set(Some(container.clone()));
|
||||||
container.clone().tl_change_extents(&pos);
|
|
||||||
self.state.damage(self.position.get());
|
self.state.damage(self.position.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2034,7 +2034,6 @@ impl Wm {
|
||||||
self.windows_by_surface_serial.remove(&serial);
|
self.windows_by_surface_serial.remove(&serial);
|
||||||
}
|
}
|
||||||
if let Some(window) = data.window.take() {
|
if let Some(window) = data.window.take() {
|
||||||
window.queue_spawn_out();
|
|
||||||
window.destroy();
|
window.destroy();
|
||||||
}
|
}
|
||||||
if let Some(parent) = data.parent.take() {
|
if let Some(parent) = data.parent.take() {
|
||||||
|
|
|
||||||
|
|
@ -64,9 +64,6 @@ pub enum SimpleCommand {
|
||||||
SetFloating(bool),
|
SetFloating(bool),
|
||||||
ToggleFullscreen,
|
ToggleFullscreen,
|
||||||
SetFullscreen(bool),
|
SetFullscreen(bool),
|
||||||
SendToScratchpad,
|
|
||||||
ToggleScratchpad,
|
|
||||||
CycleScratchpad,
|
|
||||||
Forward(bool),
|
Forward(bool),
|
||||||
EnableWindowManagement(bool),
|
EnableWindowManagement(bool),
|
||||||
SetFloatAboveFullscreen(bool),
|
SetFloatAboveFullscreen(bool),
|
||||||
|
|
@ -133,15 +130,6 @@ pub enum Action {
|
||||||
MoveToWorkspace {
|
MoveToWorkspace {
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
SendToScratchpad {
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
ToggleScratchpad {
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
CycleScratchpad {
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
Multi {
|
Multi {
|
||||||
actions: Vec<Action>,
|
actions: Vec<Action>,
|
||||||
},
|
},
|
||||||
|
|
@ -282,14 +270,7 @@ pub struct UiDrag {
|
||||||
pub struct Animations {
|
pub struct Animations {
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
pub duration_ms: Option<u32>,
|
pub duration_ms: Option<u32>,
|
||||||
pub style: Option<String>,
|
pub curve: Option<String>,
|
||||||
pub curve: Option<AnimationCurveConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum AnimationCurveConfig {
|
|
||||||
Preset(String),
|
|
||||||
CubicBezier([f32; 4]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -612,14 +593,6 @@ pub struct Config {
|
||||||
pub simple_im: Option<SimpleIm>,
|
pub simple_im: Option<SimpleIm>,
|
||||||
pub fallback_output_mode: Option<FallbackOutputMode>,
|
pub fallback_output_mode: Option<FallbackOutputMode>,
|
||||||
pub mouse_follows_focus: Option<bool>,
|
pub mouse_follows_focus: Option<bool>,
|
||||||
pub scratchpads: Vec<Scratchpad>,
|
|
||||||
pub autotile: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Scratchpad {
|
|
||||||
pub name: String,
|
|
||||||
pub exec: Option<Exec>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
@ -686,26 +659,3 @@ fn default_config_parses() {
|
||||||
let input = include_bytes!("default-config.toml");
|
let input = include_bytes!("default-config.toml");
|
||||||
parse_config(input, &Default::default(), |_| ()).unwrap();
|
parse_config(input, &Default::default(), |_| ()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn custom_animation_curve_parses() {
|
|
||||||
let input = b"
|
|
||||||
[animations]
|
|
||||||
curve = [0.25, 0.1, 0.25, 1.0]
|
|
||||||
";
|
|
||||||
let config = parse_config(input, &Default::default(), |_| ()).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
config.animations.curve,
|
|
||||||
Some(AnimationCurveConfig::CubicBezier([0.25, 0.1, 0.25, 1.0]))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn animation_style_parses() {
|
|
||||||
let input = b"
|
|
||||||
[animations]
|
|
||||||
style = \"plain\"
|
|
||||||
";
|
|
||||||
let config = parse_config(input, &Default::default(), |_| ()).unwrap();
|
|
||||||
assert_eq!(config.animations.style.as_deref(), Some("plain"));
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ pub mod modified_keysym;
|
||||||
mod output;
|
mod output;
|
||||||
mod output_match;
|
mod output_match;
|
||||||
mod repeat_rate;
|
mod repeat_rate;
|
||||||
mod scratchpad;
|
|
||||||
pub mod shortcuts;
|
pub mod shortcuts;
|
||||||
mod simple_im;
|
mod simple_im;
|
||||||
mod status;
|
mod status;
|
||||||
|
|
|
||||||
|
|
@ -117,9 +117,6 @@ impl ActionParser<'_> {
|
||||||
"toggle-fullscreen" => ToggleFullscreen,
|
"toggle-fullscreen" => ToggleFullscreen,
|
||||||
"enter-fullscreen" => SetFullscreen(true),
|
"enter-fullscreen" => SetFullscreen(true),
|
||||||
"exit-fullscreen" => SetFullscreen(false),
|
"exit-fullscreen" => SetFullscreen(false),
|
||||||
"send-to-scratchpad" => SendToScratchpad,
|
|
||||||
"toggle-scratchpad" => ToggleScratchpad,
|
|
||||||
"cycle-scratchpad" => CycleScratchpad,
|
|
||||||
"focus-parent" => FocusParent,
|
"focus-parent" => FocusParent,
|
||||||
"close" => Close,
|
"close" => Close,
|
||||||
"disable-pointer-constraint" => DisablePointerConstraint,
|
"disable-pointer-constraint" => DisablePointerConstraint,
|
||||||
|
|
@ -225,33 +222,6 @@ impl ActionParser<'_> {
|
||||||
Ok(Action::MoveToWorkspace { name })
|
Ok(Action::MoveToWorkspace { name })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_send_to_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
|
||||||
let name = ext
|
|
||||||
.extract(opt(str("name")))?
|
|
||||||
.map(|name| name.value)
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_string();
|
|
||||||
Ok(Action::SendToScratchpad { name })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_toggle_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
|
||||||
let name = ext
|
|
||||||
.extract(opt(str("name")))?
|
|
||||||
.map(|name| name.value)
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_string();
|
|
||||||
Ok(Action::ToggleScratchpad { name })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_cycle_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
|
||||||
let name = ext
|
|
||||||
.extract(opt(str("name")))?
|
|
||||||
.map(|name| name.value)
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_string();
|
|
||||||
Ok(Action::CycleScratchpad { name })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
let con = ext
|
let con = ext
|
||||||
.extract(val("connector"))?
|
.extract(val("connector"))?
|
||||||
|
|
@ -581,9 +551,6 @@ impl Parser for ActionParser<'_> {
|
||||||
"switch-to-vt" => self.parse_switch_to_vt(&mut ext),
|
"switch-to-vt" => self.parse_switch_to_vt(&mut ext),
|
||||||
"show-workspace" => self.parse_show_workspace(&mut ext),
|
"show-workspace" => self.parse_show_workspace(&mut ext),
|
||||||
"move-to-workspace" => self.parse_move_to_workspace(&mut ext),
|
"move-to-workspace" => self.parse_move_to_workspace(&mut ext),
|
||||||
"send-to-scratchpad" => self.parse_send_to_scratchpad(&mut ext),
|
|
||||||
"toggle-scratchpad" => self.parse_toggle_scratchpad(&mut ext),
|
|
||||||
"cycle-scratchpad" => self.parse_cycle_scratchpad(&mut ext),
|
|
||||||
"configure-connector" => self.parse_configure_connector(&mut ext),
|
"configure-connector" => self.parse_configure_connector(&mut ext),
|
||||||
"configure-input" => self.parse_configure_input(&mut ext),
|
"configure-input" => self.parse_configure_input(&mut ext),
|
||||||
"configure-output" => self.parse_configure_output(&mut ext),
|
"configure-output" => self.parse_configure_output(&mut ext),
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
config::{
|
config::{
|
||||||
AnimationCurveConfig, Animations,
|
Animations,
|
||||||
context::Context,
|
context::Context,
|
||||||
extractor::{Extractor, ExtractorError, bol, n32, opt, recover, str, val},
|
extractor::{Extractor, ExtractorError, bol, n32, opt, recover, str},
|
||||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
},
|
},
|
||||||
toml::{
|
toml::{
|
||||||
toml_span::{DespanExt, Span, Spanned, SpannedExt},
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
toml_value::Value,
|
toml_value::Value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -21,14 +21,6 @@ pub enum AnimationsParserError {
|
||||||
Expected(#[from] UnexpectedDataType),
|
Expected(#[from] UnexpectedDataType),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Extract(#[from] ExtractorError),
|
Extract(#[from] ExtractorError),
|
||||||
#[error("Expected animation curve to be a string or an array")]
|
|
||||||
CurveType,
|
|
||||||
#[error("Cubic-bezier animation curves must contain exactly four values")]
|
|
||||||
CubicBezierLen,
|
|
||||||
#[error("Cubic-bezier animation curve entries must be finite floats or integers")]
|
|
||||||
CubicBezierValue,
|
|
||||||
#[error("Cubic-bezier x control points must be between 0 and 1")]
|
|
||||||
CubicBezierXRange,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AnimationsParser<'a>(pub &'a Context<'a>);
|
pub struct AnimationsParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
@ -44,56 +36,15 @@ impl Parser for AnimationsParser<'_> {
|
||||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
) -> ParseResult<Self> {
|
) -> ParseResult<Self> {
|
||||||
let mut ext = Extractor::new(self.0, span, table);
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
let (enabled, duration_ms, style, curve) = ext.extract((
|
let (enabled, duration_ms, curve) = ext.extract((
|
||||||
recover(opt(bol("enabled"))),
|
recover(opt(bol("enabled"))),
|
||||||
recover(opt(n32("duration-ms"))),
|
recover(opt(n32("duration-ms"))),
|
||||||
recover(opt(str("style"))),
|
recover(opt(str("curve"))),
|
||||||
opt(val("curve")),
|
|
||||||
))?;
|
))?;
|
||||||
let curve = match curve {
|
|
||||||
Some(curve) => Some(parse_curve(curve)?),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
Ok(Animations {
|
Ok(Animations {
|
||||||
enabled: enabled.despan(),
|
enabled: enabled.despan(),
|
||||||
duration_ms: duration_ms.despan(),
|
duration_ms: duration_ms.despan(),
|
||||||
style: style.despan().map(|style| style.to_string()),
|
curve: curve.despan().map(|s| s.to_string()),
|
||||||
curve,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_curve(
|
|
||||||
curve: Spanned<&Value>,
|
|
||||||
) -> Result<AnimationCurveConfig, Spanned<AnimationsParserError>> {
|
|
||||||
match curve.value {
|
|
||||||
Value::String(s) => Ok(AnimationCurveConfig::Preset(s.clone())),
|
|
||||||
Value::Array(values) => parse_cubic_bezier(curve.span, values),
|
|
||||||
_ => Err(AnimationsParserError::CurveType.spanned(curve.span)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_cubic_bezier(
|
|
||||||
span: Span,
|
|
||||||
values: &[Spanned<Value>],
|
|
||||||
) -> Result<AnimationCurveConfig, Spanned<AnimationsParserError>> {
|
|
||||||
if values.len() != 4 {
|
|
||||||
return Err(AnimationsParserError::CubicBezierLen.spanned(span));
|
|
||||||
}
|
|
||||||
let mut points = [0.0; 4];
|
|
||||||
for (idx, value) in values.iter().enumerate() {
|
|
||||||
let f = match value.value {
|
|
||||||
Value::Float(f) => f,
|
|
||||||
Value::Integer(i) => i as f64,
|
|
||||||
_ => return Err(AnimationsParserError::CubicBezierValue.spanned(value.span)),
|
|
||||||
};
|
|
||||||
if !f.is_finite() {
|
|
||||||
return Err(AnimationsParserError::CubicBezierValue.spanned(value.span));
|
|
||||||
}
|
|
||||||
points[idx] = f as f32;
|
|
||||||
}
|
|
||||||
if !(0.0..=1.0).contains(&points[0]) || !(0.0..=1.0).contains(&points[2]) {
|
|
||||||
return Err(AnimationsParserError::CubicBezierXRange.spanned(span));
|
|
||||||
}
|
|
||||||
Ok(AnimationCurveConfig::CubicBezier(points))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ use {
|
||||||
log_level::LogLevelParser,
|
log_level::LogLevelParser,
|
||||||
output::OutputsParser,
|
output::OutputsParser,
|
||||||
repeat_rate::RepeatRateParser,
|
repeat_rate::RepeatRateParser,
|
||||||
scratchpad::ScratchpadsParser,
|
|
||||||
shortcuts::{
|
shortcuts::{
|
||||||
ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError,
|
ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError,
|
||||||
parse_modified_keysym_str,
|
parse_modified_keysym_str,
|
||||||
|
|
@ -157,7 +156,6 @@ impl Parser for ConfigParser<'_> {
|
||||||
mouse_follows_focus,
|
mouse_follows_focus,
|
||||||
animations_val,
|
animations_val,
|
||||||
),
|
),
|
||||||
(scratchpads_val, autotile),
|
|
||||||
) = ext.extract((
|
) = ext.extract((
|
||||||
(
|
(
|
||||||
opt(val("keymap")),
|
opt(val("keymap")),
|
||||||
|
|
@ -219,7 +217,6 @@ impl Parser for ConfigParser<'_> {
|
||||||
recover(opt(bol("unstable-mouse-follows-focus"))),
|
recover(opt(bol("unstable-mouse-follows-focus"))),
|
||||||
opt(val("animations")),
|
opt(val("animations")),
|
||||||
),
|
),
|
||||||
(opt(val("scratchpads")), recover(opt(bol("autotile")))),
|
|
||||||
))?;
|
))?;
|
||||||
let mut keymap = None;
|
let mut keymap = None;
|
||||||
if let Some(value) = keymap_val {
|
if let Some(value) = keymap_val {
|
||||||
|
|
@ -571,13 +568,6 @@ impl Parser for ConfigParser<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut scratchpads = vec![];
|
|
||||||
if let Some(value) = scratchpads_val {
|
|
||||||
match value.parse(&mut ScratchpadsParser(self.0)) {
|
|
||||||
Ok(v) => scratchpads = v,
|
|
||||||
Err(e) => log::warn!("Could not parse the scratchpads: {}", self.0.error(e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Config {
|
Ok(Config {
|
||||||
keymap,
|
keymap,
|
||||||
repeat_rate,
|
repeat_rate,
|
||||||
|
|
@ -628,8 +618,6 @@ impl Parser for ConfigParser<'_> {
|
||||||
simple_im,
|
simple_im,
|
||||||
fallback_output_mode,
|
fallback_output_mode,
|
||||||
mouse_follows_focus: mouse_follows_focus.despan(),
|
mouse_follows_focus: mouse_follows_focus.despan(),
|
||||||
scratchpads,
|
|
||||||
autotile: autotile.despan(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
config::{
|
|
||||||
Scratchpad,
|
|
||||||
context::Context,
|
|
||||||
extractor::{Extractor, ExtractorError, opt, str, val},
|
|
||||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
|
||||||
parsers::exec::{ExecParser, ExecParserError},
|
|
||||||
},
|
|
||||||
toml::{
|
|
||||||
toml_span::{Span, Spanned},
|
|
||||||
toml_value::Value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
indexmap::IndexMap,
|
|
||||||
thiserror::Error,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum ScratchpadParserError {
|
|
||||||
#[error(transparent)]
|
|
||||||
Expected(#[from] UnexpectedDataType),
|
|
||||||
#[error(transparent)]
|
|
||||||
Extract(#[from] ExtractorError),
|
|
||||||
#[error(transparent)]
|
|
||||||
Exec(#[from] ExecParserError),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ScratchpadParser<'a>(pub &'a Context<'a>);
|
|
||||||
|
|
||||||
impl Parser for ScratchpadParser<'_> {
|
|
||||||
type Value = Scratchpad;
|
|
||||||
type Error = ScratchpadParserError;
|
|
||||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
|
||||||
|
|
||||||
fn parse_table(
|
|
||||||
&mut self,
|
|
||||||
span: Span,
|
|
||||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
|
||||||
) -> ParseResult<Self> {
|
|
||||||
let mut ext = Extractor::new(self.0, span, table);
|
|
||||||
let (name, exec_val) = ext.extract((str("name"), opt(val("exec"))))?;
|
|
||||||
let exec = match exec_val {
|
|
||||||
None => None,
|
|
||||||
Some(e) => Some(e.parse_map(&mut ExecParser(self.0))?),
|
|
||||||
};
|
|
||||||
Ok(Scratchpad {
|
|
||||||
name: name.value.to_string(),
|
|
||||||
exec,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ScratchpadsParser<'a>(pub &'a Context<'a>);
|
|
||||||
|
|
||||||
impl Parser for ScratchpadsParser<'_> {
|
|
||||||
type Value = Vec<Scratchpad>;
|
|
||||||
type Error = ScratchpadParserError;
|
|
||||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
|
||||||
|
|
||||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
|
||||||
let mut res = vec![];
|
|
||||||
for el in array {
|
|
||||||
match el.parse(&mut ScratchpadParser(self.0)) {
|
|
||||||
Ok(o) => res.push(o),
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("Could not parse scratchpad: {}", self.0.error(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_table(
|
|
||||||
&mut self,
|
|
||||||
span: Span,
|
|
||||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
|
||||||
) -> ParseResult<Self> {
|
|
||||||
log::warn!(
|
|
||||||
"`scratchpads` value should be an array: {}",
|
|
||||||
self.0.error3(span)
|
|
||||||
);
|
|
||||||
ScratchpadParser(self.0)
|
|
||||||
.parse_table(span, table)
|
|
||||||
.map(|v| vec![v])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,9 +13,9 @@ mod toml;
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
config::{
|
config::{
|
||||||
Action, AnimationCurveConfig, ClientRule, Config, ConfigConnector, ConfigDrmDevice,
|
Action, ClientRule, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap,
|
||||||
ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output,
|
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch,
|
||||||
OutputMatch, SimpleCommand, Status, Theme, WindowMatch, WindowRule, parse_config,
|
SimpleCommand, Status, Theme, WindowRule, parse_config,
|
||||||
},
|
},
|
||||||
rules::{MatcherTemp, RuleMapper},
|
rules::{MatcherTemp, RuleMapper},
|
||||||
shortcuts::ModeState,
|
shortcuts::ModeState,
|
||||||
|
|
@ -23,11 +23,11 @@ use {
|
||||||
ahash::{AHashMap, AHashSet},
|
ahash::{AHashMap, AHashSet},
|
||||||
error_reporter::Report,
|
error_reporter::Report,
|
||||||
jay_config::{
|
jay_config::{
|
||||||
AnimationCurve, AnimationStyle, Axis,
|
AnimationCurve, Axis,
|
||||||
client::Client,
|
client::Client,
|
||||||
config, config_dir,
|
config, config_dir,
|
||||||
exec::{Command, set_env, unset_env},
|
exec::{Command, set_env, unset_env},
|
||||||
get_autotile, get_workspace,
|
get_workspace,
|
||||||
input::{
|
input::{
|
||||||
FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, capability::CAP_SWITCH,
|
FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, capability::CAP_SWITCH,
|
||||||
get_seat, input_devices, on_input_device_removed, on_new_input_device,
|
get_seat, input_devices, on_input_device_removed, on_new_input_device,
|
||||||
|
|
@ -37,13 +37,13 @@ use {
|
||||||
is_reload,
|
is_reload,
|
||||||
keyboard::Keymap,
|
keyboard::Keymap,
|
||||||
logging::{clean_logs_older_than, set_log_level},
|
logging::{clean_logs_older_than, set_log_level},
|
||||||
on_devices_enumerated, on_idle, on_unload, quit, reload, set_animation_cubic_bezier,
|
on_devices_enumerated, on_idle, on_unload, quit, reload, set_animation_curve,
|
||||||
set_animation_curve, set_animation_duration_ms, set_animation_style,
|
set_animation_duration_ms, set_animations_enabled, set_autotile,
|
||||||
set_animations_enabled, set_autotile, set_color_management_enabled, set_corner_radius,
|
set_color_management_enabled, set_corner_radius, set_default_workspace_capture,
|
||||||
set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen,
|
set_explicit_sync_enabled, set_float_above_fullscreen, set_floating_titles, set_idle,
|
||||||
set_floating_titles, set_idle, set_idle_grace_period, set_middle_click_paste_enabled,
|
set_idle_grace_period, set_middle_click_paste_enabled, set_show_bar,
|
||||||
set_show_bar, set_show_float_pin_icon, set_show_titles, set_tab_title_align,
|
set_show_float_pin_icon, set_show_titles, set_tab_title_align, set_ui_drag_enabled,
|
||||||
set_ui_drag_enabled, set_ui_drag_threshold,
|
set_ui_drag_threshold,
|
||||||
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
||||||
switch_to_vt,
|
switch_to_vt,
|
||||||
tasks::{self, JoinHandle},
|
tasks::{self, JoinHandle},
|
||||||
|
|
@ -173,9 +173,6 @@ impl Action {
|
||||||
SimpleCommand::Move(dir) => window_or_seat!(s, s.move_(dir)),
|
SimpleCommand::Move(dir) => window_or_seat!(s, s.move_(dir)),
|
||||||
SimpleCommand::ToggleFullscreen => window_or_seat!(s, s.toggle_fullscreen()),
|
SimpleCommand::ToggleFullscreen => window_or_seat!(s, s.toggle_fullscreen()),
|
||||||
SimpleCommand::SetFullscreen(b) => window_or_seat!(s, s.set_fullscreen(b)),
|
SimpleCommand::SetFullscreen(b) => window_or_seat!(s, s.set_fullscreen(b)),
|
||||||
SimpleCommand::SendToScratchpad => window_or_seat!(s, s.send_to_scratchpad("")),
|
|
||||||
SimpleCommand::ToggleScratchpad => b.new(move || s.toggle_scratchpad("")),
|
|
||||||
SimpleCommand::CycleScratchpad => b.new(move || s.cycle_scratchpad("")),
|
|
||||||
SimpleCommand::FocusParent => b.new(move || s.focus_parent()),
|
SimpleCommand::FocusParent => b.new(move || s.focus_parent()),
|
||||||
SimpleCommand::Close => window_or_seat!(s, s.close()),
|
SimpleCommand::Close => window_or_seat!(s, s.close()),
|
||||||
SimpleCommand::DisablePointerConstraint => {
|
SimpleCommand::DisablePointerConstraint => {
|
||||||
|
|
@ -272,7 +269,12 @@ impl Action {
|
||||||
SimpleCommand::MoveTabLeft => b.new(move || s.move_tab(false)),
|
SimpleCommand::MoveTabLeft => b.new(move || s.move_tab(false)),
|
||||||
SimpleCommand::MoveTabRight => b.new(move || s.move_tab(true)),
|
SimpleCommand::MoveTabRight => b.new(move || s.move_tab(true)),
|
||||||
SimpleCommand::SetAutotile(enabled) => b.new(move || set_autotile(enabled)),
|
SimpleCommand::SetAutotile(enabled) => b.new(move || set_autotile(enabled)),
|
||||||
SimpleCommand::ToggleAutotile => b.new(move || set_autotile(!get_autotile())),
|
SimpleCommand::ToggleAutotile => {
|
||||||
|
b.new(move || {
|
||||||
|
// Toggle not directly supported; set to true
|
||||||
|
set_autotile(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Action::Multi { actions } => {
|
Action::Multi { actions } => {
|
||||||
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
||||||
|
|
@ -309,9 +311,6 @@ impl Action {
|
||||||
let workspace = get_workspace(&name);
|
let workspace = get_workspace(&name);
|
||||||
window_or_seat!(s, s.set_workspace(workspace))
|
window_or_seat!(s, s.set_workspace(workspace))
|
||||||
}
|
}
|
||||||
Action::SendToScratchpad { name } => window_or_seat!(s, s.send_to_scratchpad(&name)),
|
|
||||||
Action::ToggleScratchpad { name } => b.new(move || s.toggle_scratchpad(&name)),
|
|
||||||
Action::CycleScratchpad { name } => b.new(move || s.cycle_scratchpad(&name)),
|
|
||||||
Action::ConfigureConnector { con } => b.new(move || {
|
Action::ConfigureConnector { con } => b.new(move || {
|
||||||
for c in connectors() {
|
for c in connectors() {
|
||||||
if con.match_.matches(c) {
|
if con.match_.matches(c) {
|
||||||
|
|
@ -1463,43 +1462,6 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
|
||||||
window: Default::default(),
|
window: Default::default(),
|
||||||
});
|
});
|
||||||
state.clear_modes_after_reload();
|
state.clear_modes_after_reload();
|
||||||
// Desugar `[[scratchpads]]` into spawn-on-graphics-init plus an internal
|
|
||||||
// window rule that parks the spawned window. Each spawned process gets a
|
|
||||||
// unique tag so only its own windows are captured, never other windows of
|
|
||||||
// the same application.
|
|
||||||
if !config.scratchpads.is_empty() {
|
|
||||||
let mut spawn_actions = vec![];
|
|
||||||
for (i, sp) in config.scratchpads.drain(..).enumerate() {
|
|
||||||
let Some(mut exec) = sp.exec else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let tag = exec
|
|
||||||
.tag
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| format!("__scratchpad.{i}.{}", sp.name));
|
|
||||||
exec.tag = Some(tag.clone());
|
|
||||||
spawn_actions.push(Action::Exec { exec });
|
|
||||||
config.window_rules.push(WindowRule {
|
|
||||||
name: None,
|
|
||||||
match_: WindowMatch {
|
|
||||||
tag: Some(tag),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
action: Some(Action::SendToScratchpad { name: sp.name }),
|
|
||||||
latch: None,
|
|
||||||
auto_focus: None,
|
|
||||||
initial_tile_state: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if !spawn_actions.is_empty() {
|
|
||||||
let mut actions = Vec::with_capacity(spawn_actions.len() + 1);
|
|
||||||
if let Some(existing) = config.on_graphics_initialized.take() {
|
|
||||||
actions.push(existing);
|
|
||||||
}
|
|
||||||
actions.extend(spawn_actions);
|
|
||||||
config.on_graphics_initialized = Some(Action::Multi { actions });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let (client_rules, client_rule_mapper) = state.create_rules(&config.client_rules);
|
let (client_rules, client_rule_mapper) = state.create_rules(&config.client_rules);
|
||||||
persistent.client_rules.set(client_rules);
|
persistent.client_rules.set(client_rules);
|
||||||
*state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
|
*state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
|
||||||
|
|
@ -1690,35 +1652,20 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
|
||||||
}
|
}
|
||||||
set_animations_enabled(config.animations.enabled.unwrap_or(false));
|
set_animations_enabled(config.animations.enabled.unwrap_or(false));
|
||||||
set_animation_duration_ms(config.animations.duration_ms.unwrap_or(160));
|
set_animation_duration_ms(config.animations.duration_ms.unwrap_or(160));
|
||||||
match config.animations.style.as_deref().unwrap_or("multiphase") {
|
let curve_name = config.animations.curve.as_deref().unwrap_or("ease-out");
|
||||||
"plain" => set_animation_style(AnimationStyle::PLAIN),
|
let curve = match curve_name {
|
||||||
"multiphase" => set_animation_style(AnimationStyle::MULTIPHASE),
|
"linear" => Some(AnimationCurve::LINEAR),
|
||||||
style_name => log::warn!("Unknown animation style: {style_name}"),
|
"ease" => Some(AnimationCurve::EASE),
|
||||||
}
|
"ease-in" => Some(AnimationCurve::EASE_IN),
|
||||||
match config
|
"ease-out" => Some(AnimationCurve::EASE_OUT),
|
||||||
.animations
|
"ease-in-out" => Some(AnimationCurve::EASE_IN_OUT),
|
||||||
.curve
|
_ => {
|
||||||
.unwrap_or_else(|| AnimationCurveConfig::Preset("ease-out".to_string()))
|
log::warn!("Unknown animation curve: {curve_name}");
|
||||||
{
|
None
|
||||||
AnimationCurveConfig::Preset(curve_name) => {
|
|
||||||
let curve = match curve_name.as_str() {
|
|
||||||
"linear" => Some(AnimationCurve::LINEAR),
|
|
||||||
"ease" => Some(AnimationCurve::EASE),
|
|
||||||
"ease-in" => Some(AnimationCurve::EASE_IN),
|
|
||||||
"ease-out" => Some(AnimationCurve::EASE_OUT),
|
|
||||||
"ease-in-out" => Some(AnimationCurve::EASE_IN_OUT),
|
|
||||||
_ => {
|
|
||||||
log::warn!("Unknown animation curve: {curve_name}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(curve) = curve {
|
|
||||||
set_animation_curve(curve);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AnimationCurveConfig::CubicBezier([x1, y1, x2, y2]) => {
|
|
||||||
set_animation_cubic_bezier(x1, y1, x2, y2);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
if let Some(curve) = curve {
|
||||||
|
set_animation_curve(curve);
|
||||||
}
|
}
|
||||||
if let Some(xwayland) = config.xwayland {
|
if let Some(xwayland) = config.xwayland {
|
||||||
if let Some(enabled) = xwayland.enabled {
|
if let Some(enabled) = xwayland.enabled {
|
||||||
|
|
@ -1784,9 +1731,6 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
|
||||||
.seat
|
.seat
|
||||||
.unstable_set_mouse_follows_focus(mouse_follows_focus);
|
.unstable_set_mouse_follows_focus(mouse_follows_focus);
|
||||||
}
|
}
|
||||||
if let Some(v) = config.autotile {
|
|
||||||
set_autotile(v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_command(exec: &Exec) -> Command {
|
fn create_command(exec: &Exec) -> Command {
|
||||||
|
|
|
||||||
|
|
@ -162,54 +162,6 @@
|
||||||
"name"
|
"name"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"description": "Sends the currently focused window to a scratchpad and hides it.\n\nA scratchpad can hold any number of windows. If `name` is omitted, the\ndefault scratchpad is used.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-shift-minus = { type = \"send-to-scratchpad\", name = \"terminal\" }\n ```\n",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"const": "send-to-scratchpad"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The name of the scratchpad."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"type"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Toggles a scratchpad.\n\nIf the scratchpad has a visible window, that window is hidden. Otherwise, the\nmost recently hidden window in the scratchpad is shown on the current workspace.\nOnly one window of a scratchpad is shown at a time, and scratchpad windows are\nalways shown floating. If `name` is omitted, the default scratchpad is used.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-minus = { type = \"toggle-scratchpad\", name = \"terminal\" }\n ```\n",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"const": "toggle-scratchpad"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The name of the scratchpad."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"type"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Cycles through the windows of a scratchpad, one at a time.\n\nWith no window shown, the first window is brought up. Each further invocation\nhides the current window and shows the next; after the last window the\nscratchpad is hidden again. Scratchpad windows are always shown floating.\nIf `name` is omitted, the default scratchpad is used.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-minus = { type = \"cycle-scratchpad\", name = \"terminal\" }\n ```\n",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"const": "cycle-scratchpad"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The name of the scratchpad."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"type"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"description": "Moves a workspace to a different output.\n\n- Example 1: Move a specific workspace to a named output\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", workspace = \"1\", output.name = \"right\" }\n ```\n\n- Example 2: Move the current workspace to a named output\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", output.name = \"right\" }\n ```\n\n- Example 3: Move the current workspace to the output on the right (directional)\n\n ```toml\n [shortcuts]\n \"logo+ctrl+shift+Right\" = { type = \"move-to-output\", direction = \"right\" }\n \"logo+ctrl+shift+Left\" = { type = \"move-to-output\", direction = \"left\" }\n \"logo+ctrl+shift+Up\" = { type = \"move-to-output\", direction = \"up\" }\n \"logo+ctrl+shift+Down\" = { type = \"move-to-output\", direction = \"down\" }\n ```\n",
|
"description": "Moves a workspace to a different output.\n\n- Example 1: Move a specific workspace to a named output\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", workspace = \"1\", output.name = \"right\" }\n ```\n\n- Example 2: Move the current workspace to a named output\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", output.name = \"right\" }\n ```\n\n- Example 3: Move the current workspace to the output on the right (directional)\n\n ```toml\n [shortcuts]\n \"logo+ctrl+shift+Right\" = { type = \"move-to-output\", direction = \"right\" }\n \"logo+ctrl+shift+Left\" = { type = \"move-to-output\", direction = \"left\" }\n \"logo+ctrl+shift+Up\" = { type = \"move-to-output\", direction = \"up\" }\n \"logo+ctrl+shift+Down\" = { type = \"move-to-output\", direction = \"down\" }\n ```\n",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -689,61 +641,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"AnimationCurve": {
|
|
||||||
"description": "Describes a window animation curve.\n",
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "One of the supported curve presets.\n",
|
|
||||||
"enum": [
|
|
||||||
"linear",
|
|
||||||
"ease",
|
|
||||||
"ease-in",
|
|
||||||
"ease-out",
|
|
||||||
"ease-in-out"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "array",
|
|
||||||
"description": "A custom CSS-style cubic-bezier curve as four numbers:\n`x1`, `y1`, `x2`, and `y2`.\n\nThe implicit endpoints are `(0, 0)` and `(1, 1)`. `x1` and `x2` must\nbe between `0` and `1`.\n",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"AnimationStyle": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Describes a tiled window movement animation style.\n",
|
|
||||||
"enum": [
|
|
||||||
"plain",
|
|
||||||
"multiphase"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Animations": {
|
|
||||||
"description": "Describes window animation settings.\n\n- Example:\n\n ```toml\n [animations]\n enabled = true\n duration-ms = 160\n style = \"multiphase\"\n curve = [0.25, 0.1, 0.25, 1.0]\n ```\n",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"enabled": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Enables or disables window animations.\n\nThe default is `false`.\n"
|
|
||||||
},
|
|
||||||
"duration-ms": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Sets the animation duration in milliseconds.\n\nThe default is `160`.\n"
|
|
||||||
},
|
|
||||||
"style": {
|
|
||||||
"description": "Sets the animation style used for tiled window movement animations.\n\nThe default is `multiphase`.\n",
|
|
||||||
"$ref": "#/$defs/AnimationStyle"
|
|
||||||
},
|
|
||||||
"curve": {
|
|
||||||
"description": "Sets the animation curve.\n\nThe default is `ease-out`.\n",
|
|
||||||
"$ref": "#/$defs/AnimationCurve"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": []
|
|
||||||
},
|
|
||||||
"BarPosition": {
|
"BarPosition": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The position of the bar.",
|
"description": "The position of the bar.",
|
||||||
|
|
@ -1188,10 +1085,6 @@
|
||||||
"description": "Configures the ui-drag settings.\n\n- Example:\n\n ```toml\n ui-drag = { enabled = false, threshold = 20 }\n ```\n",
|
"description": "Configures the ui-drag settings.\n\n- Example:\n\n ```toml\n ui-drag = { enabled = false, threshold = 20 }\n ```\n",
|
||||||
"$ref": "#/$defs/UiDrag"
|
"$ref": "#/$defs/UiDrag"
|
||||||
},
|
},
|
||||||
"animations": {
|
|
||||||
"description": "Configures window animations.\n\nAnimations are disabled by default.\n\n- Example:\n\n ```toml\n [animations]\n enabled = true\n duration-ms = 160\n style = \"multiphase\"\n curve = \"ease-out\"\n ```\n",
|
|
||||||
"$ref": "#/$defs/Animations"
|
|
||||||
},
|
|
||||||
"xwayland": {
|
"xwayland": {
|
||||||
"description": "Configures the Xwayland settings.\n\n- Example:\n\n ```toml\n xwayland = { scaling-mode = \"downscaled\" }\n ```\n",
|
"description": "Configures the Xwayland settings.\n\n- Example:\n\n ```toml\n xwayland = { scaling-mode = \"downscaled\" }\n ```\n",
|
||||||
"$ref": "#/$defs/Xwayland"
|
"$ref": "#/$defs/Xwayland"
|
||||||
|
|
@ -1257,10 +1150,6 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Configures whether middle-click pasting is enabled.\n\nChanging this has no effect on running applications.\n\nThe default is `true`.\n"
|
"description": "Configures whether middle-click pasting is enabled.\n\nChanging this has no effect on running applications.\n\nThe default is `true`.\n"
|
||||||
},
|
},
|
||||||
"autotile": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Configures whether autotiling is enabled by default.\n\nWhen enabled, newly mapped tiled windows alternate their split\norientation automatically. This can also be toggled at runtime via the\n`enable-autotile`, `disable-autotile`, and `toggle-autotile` actions.\n\nThe default is `false`.\n"
|
|
||||||
},
|
|
||||||
"modes": {
|
"modes": {
|
||||||
"description": "Configures the input modes.\n\nModes can be used to define shortcuts that are only active when the mode is\nactive.\n\n- Example\n\n ```toml\n [modes.\"navigation\".shortcuts]\n w = \"focus-up\"\n a = \"focus-left\"\n s = \"focus-down\"\n d = \"focus-right\"\n r = \"focus-above\"\n f = \"focus-below\"\n q = \"focus-prev\"\n e = \"focus-next\"\n ```\n\nModes can be activated with the `push-mode` and `latch-mode` actions.\n",
|
"description": "Configures the input modes.\n\nModes can be used to define shortcuts that are only active when the mode is\nactive.\n\n- Example\n\n ```toml\n [modes.\"navigation\".shortcuts]\n w = \"focus-up\"\n a = \"focus-left\"\n s = \"focus-down\"\n d = \"focus-right\"\n r = \"focus-above\"\n f = \"focus-below\"\n q = \"focus-prev\"\n e = \"focus-next\"\n ```\n\nModes can be activated with the `push-mode` and `latch-mode` actions.\n",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -1288,14 +1177,6 @@
|
||||||
"egui": {
|
"egui": {
|
||||||
"description": "Sets the egui settings of the compositor.\n",
|
"description": "Sets the egui settings of the compositor.\n",
|
||||||
"$ref": "#/$defs/Egui"
|
"$ref": "#/$defs/Egui"
|
||||||
},
|
|
||||||
"scratchpads": {
|
|
||||||
"type": "array",
|
|
||||||
"description": "An array of pre-configured scratchpads.\n\nEach entry launches a program when the graphics are first initialized and\nimmediately parks its window in the named scratchpad. The window is captured\nvia a unique tag attached to the spawned process, so other windows of the\nsame application are never affected.\n\nUse a `toggle-scratchpad` or `cycle-scratchpad` action to bring the windows\nup; they are always shown floating.\n\n- Example:\n\n ```toml\n [[scratchpads]]\n name = \"term\"\n exec = \"foot\"\n\n [[scratchpads]]\n name = \"notes\"\n exec = [\"obsidian\"]\n ```\n",
|
|
||||||
"items": {
|
|
||||||
"description": "",
|
|
||||||
"$ref": "#/$defs/Scratchpad"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
|
@ -2110,23 +1991,6 @@
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
},
|
},
|
||||||
"Scratchpad": {
|
|
||||||
"description": "A pre-configured scratchpad whose program is launched at startup and parked\nin the scratchpad.\n\n- Example:\n\n ```toml\n [[scratchpads]]\n name = \"term\"\n exec = \"foot\"\n ```\n",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The name of the scratchpad that the spawned window is parked in."
|
|
||||||
},
|
|
||||||
"exec": {
|
|
||||||
"description": "The program to launch when the graphics are first initialized.\n\nIf omitted, no program is launched and the scratchpad is only created on\ndemand by `send-to-scratchpad`.\n",
|
|
||||||
"$ref": "#/$defs/Exec"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"name"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"SimpleActionName": {
|
"SimpleActionName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The name of a `simple` Action.\n\nWhen used inside a window rule, the following actions apply to the matched window\ninstead fo the focused window:\n\n- `move-left`\n- `move-down`\n- `move-up`\n- `move-right`\n- `toggle-fullscreen`\n- `enter-fullscreen`\n- `exit-fullscreen`\n- `close`\n- `toggle-floating`\n- `float`\n- `tile`\n- `toggle-float-pinned`\n- `pin-float`\n- `unpin-float`\n\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n",
|
"description": "The name of a `simple` Action.\n\nWhen used inside a window rule, the following actions apply to the matched window\ninstead fo the focused window:\n\n- `move-left`\n- `move-down`\n- `move-up`\n- `move-right`\n- `toggle-fullscreen`\n- `enter-fullscreen`\n- `exit-fullscreen`\n- `close`\n- `toggle-floating`\n- `float`\n- `tile`\n- `toggle-float-pinned`\n- `pin-float`\n- `unpin-float`\n\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n",
|
||||||
|
|
@ -2145,15 +2009,9 @@
|
||||||
"make-group-tab",
|
"make-group-tab",
|
||||||
"change-group-opposite",
|
"change-group-opposite",
|
||||||
"toggle-tab",
|
"toggle-tab",
|
||||||
"enable-autotile",
|
|
||||||
"disable-autotile",
|
|
||||||
"toggle-autotile",
|
|
||||||
"toggle-fullscreen",
|
"toggle-fullscreen",
|
||||||
"enter-fullscreen",
|
"enter-fullscreen",
|
||||||
"exit-fullscreen",
|
"exit-fullscreen",
|
||||||
"send-to-scratchpad",
|
|
||||||
"toggle-scratchpad",
|
|
||||||
"cycle-scratchpad",
|
|
||||||
"focus-parent",
|
"focus-parent",
|
||||||
"close",
|
"close",
|
||||||
"disable-pointer-constraint",
|
"disable-pointer-constraint",
|
||||||
|
|
|
||||||
|
|
@ -286,76 +286,6 @@ This table is a tagged union. The variant is determined by the `type` field. It
|
||||||
|
|
||||||
The value of this field should be a string.
|
The value of this field should be a string.
|
||||||
|
|
||||||
- `send-to-scratchpad`:
|
|
||||||
|
|
||||||
Sends the currently focused window to a scratchpad and hides it.
|
|
||||||
|
|
||||||
A scratchpad can hold any number of windows. If `name` is omitted, the
|
|
||||||
default scratchpad is used.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[shortcuts]
|
|
||||||
alt-shift-minus = { type = "send-to-scratchpad", name = "terminal" }
|
|
||||||
```
|
|
||||||
|
|
||||||
The table has the following fields:
|
|
||||||
|
|
||||||
- `name` (optional):
|
|
||||||
|
|
||||||
The name of the scratchpad.
|
|
||||||
|
|
||||||
The value of this field should be a string.
|
|
||||||
|
|
||||||
- `toggle-scratchpad`:
|
|
||||||
|
|
||||||
Toggles a scratchpad.
|
|
||||||
|
|
||||||
If the scratchpad has a visible window, that window is hidden. Otherwise, the
|
|
||||||
most recently hidden window in the scratchpad is shown on the current workspace.
|
|
||||||
Only one window of a scratchpad is shown at a time, and scratchpad windows are
|
|
||||||
always shown floating. If `name` is omitted, the default scratchpad is used.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[shortcuts]
|
|
||||||
alt-minus = { type = "toggle-scratchpad", name = "terminal" }
|
|
||||||
```
|
|
||||||
|
|
||||||
The table has the following fields:
|
|
||||||
|
|
||||||
- `name` (optional):
|
|
||||||
|
|
||||||
The name of the scratchpad.
|
|
||||||
|
|
||||||
The value of this field should be a string.
|
|
||||||
|
|
||||||
- `cycle-scratchpad`:
|
|
||||||
|
|
||||||
Cycles through the windows of a scratchpad, one at a time.
|
|
||||||
|
|
||||||
With no window shown, the first window is brought up. Each further invocation
|
|
||||||
hides the current window and shows the next; after the last window the
|
|
||||||
scratchpad is hidden again. Scratchpad windows are always shown floating.
|
|
||||||
If `name` is omitted, the default scratchpad is used.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[shortcuts]
|
|
||||||
alt-minus = { type = "cycle-scratchpad", name = "terminal" }
|
|
||||||
```
|
|
||||||
|
|
||||||
The table has the following fields:
|
|
||||||
|
|
||||||
- `name` (optional):
|
|
||||||
|
|
||||||
The name of the scratchpad.
|
|
||||||
|
|
||||||
The value of this field should be a string.
|
|
||||||
|
|
||||||
- `move-to-output`:
|
- `move-to-output`:
|
||||||
|
|
||||||
Moves a workspace to a different output.
|
Moves a workspace to a different output.
|
||||||
|
|
@ -1012,126 +942,6 @@ This table is a tagged union. The variant is determined by the `type` field. It
|
||||||
The numbers should be integers.
|
The numbers should be integers.
|
||||||
|
|
||||||
|
|
||||||
<a name="types-AnimationCurve"></a>
|
|
||||||
### `AnimationCurve`
|
|
||||||
|
|
||||||
Describes a window animation curve.
|
|
||||||
|
|
||||||
Values of this type should have one of the following forms:
|
|
||||||
|
|
||||||
#### A string
|
|
||||||
|
|
||||||
One of the supported curve presets.
|
|
||||||
|
|
||||||
The string should have one of the following values:
|
|
||||||
|
|
||||||
- `linear`:
|
|
||||||
|
|
||||||
No easing.
|
|
||||||
|
|
||||||
- `ease`:
|
|
||||||
|
|
||||||
The CSS `ease` curve.
|
|
||||||
|
|
||||||
- `ease-in`:
|
|
||||||
|
|
||||||
The CSS `ease-in` curve.
|
|
||||||
|
|
||||||
- `ease-out`:
|
|
||||||
|
|
||||||
The CSS `ease-out` curve.
|
|
||||||
|
|
||||||
- `ease-in-out`:
|
|
||||||
|
|
||||||
The CSS `ease-in-out` curve.
|
|
||||||
|
|
||||||
|
|
||||||
#### An array
|
|
||||||
|
|
||||||
A custom CSS-style cubic-bezier curve as four numbers:
|
|
||||||
`x1`, `y1`, `x2`, and `y2`.
|
|
||||||
|
|
||||||
The implicit endpoints are `(0, 0)` and `(1, 1)`. `x1` and `x2` must
|
|
||||||
be between `0` and `1`.
|
|
||||||
|
|
||||||
Each element of this array should be a number.
|
|
||||||
|
|
||||||
|
|
||||||
<a name="types-AnimationStyle"></a>
|
|
||||||
### `AnimationStyle`
|
|
||||||
|
|
||||||
Describes a tiled window movement animation style.
|
|
||||||
|
|
||||||
Values of this type should be strings.
|
|
||||||
|
|
||||||
The string should have one of the following values:
|
|
||||||
|
|
||||||
- `plain`:
|
|
||||||
|
|
||||||
Uses a single interpolated movement from each window's current visual
|
|
||||||
rectangle to its destination rectangle.
|
|
||||||
|
|
||||||
- `multiphase`:
|
|
||||||
|
|
||||||
Uses the no-overlap multiphase planner for tiled window movement when a
|
|
||||||
supported plan exists.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Animations"></a>
|
|
||||||
### `Animations`
|
|
||||||
|
|
||||||
Describes window animation settings.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[animations]
|
|
||||||
enabled = true
|
|
||||||
duration-ms = 160
|
|
||||||
style = "multiphase"
|
|
||||||
curve = [0.25, 0.1, 0.25, 1.0]
|
|
||||||
```
|
|
||||||
|
|
||||||
Values of this type should be tables.
|
|
||||||
|
|
||||||
The table has the following fields:
|
|
||||||
|
|
||||||
- `enabled` (optional):
|
|
||||||
|
|
||||||
Enables or disables window animations.
|
|
||||||
|
|
||||||
The default is `false`.
|
|
||||||
|
|
||||||
The value of this field should be a boolean.
|
|
||||||
|
|
||||||
- `duration-ms` (optional):
|
|
||||||
|
|
||||||
Sets the animation duration in milliseconds.
|
|
||||||
|
|
||||||
The default is `160`.
|
|
||||||
|
|
||||||
The value of this field should be a number.
|
|
||||||
|
|
||||||
The numbers should be integers.
|
|
||||||
|
|
||||||
- `style` (optional):
|
|
||||||
|
|
||||||
Sets the animation style used for tiled window movement animations.
|
|
||||||
|
|
||||||
The default is `multiphase`.
|
|
||||||
|
|
||||||
The value of this field should be a [AnimationStyle](#types-AnimationStyle).
|
|
||||||
|
|
||||||
- `curve` (optional):
|
|
||||||
|
|
||||||
Sets the animation curve.
|
|
||||||
|
|
||||||
The default is `ease-out`.
|
|
||||||
|
|
||||||
The value of this field should be a [AnimationCurve](#types-AnimationCurve).
|
|
||||||
|
|
||||||
|
|
||||||
<a name="types-BarPosition"></a>
|
<a name="types-BarPosition"></a>
|
||||||
### `BarPosition`
|
### `BarPosition`
|
||||||
|
|
||||||
|
|
@ -2359,24 +2169,6 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a [UiDrag](#types-UiDrag).
|
The value of this field should be a [UiDrag](#types-UiDrag).
|
||||||
|
|
||||||
- `animations` (optional):
|
|
||||||
|
|
||||||
Configures window animations.
|
|
||||||
|
|
||||||
Animations are disabled by default.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[animations]
|
|
||||||
enabled = true
|
|
||||||
duration-ms = 160
|
|
||||||
style = "multiphase"
|
|
||||||
curve = "ease-out"
|
|
||||||
```
|
|
||||||
|
|
||||||
The value of this field should be a [Animations](#types-Animations).
|
|
||||||
|
|
||||||
- `xwayland` (optional):
|
- `xwayland` (optional):
|
||||||
|
|
||||||
Configures the Xwayland settings.
|
Configures the Xwayland settings.
|
||||||
|
|
@ -2560,18 +2352,6 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a boolean.
|
The value of this field should be a boolean.
|
||||||
|
|
||||||
- `autotile` (optional):
|
|
||||||
|
|
||||||
Configures whether autotiling is enabled by default.
|
|
||||||
|
|
||||||
When enabled, newly mapped tiled windows alternate their split
|
|
||||||
orientation automatically. This can also be toggled at runtime via the
|
|
||||||
`enable-autotile`, `disable-autotile`, and `toggle-autotile` actions.
|
|
||||||
|
|
||||||
The default is `false`.
|
|
||||||
|
|
||||||
The value of this field should be a boolean.
|
|
||||||
|
|
||||||
- `modes` (optional):
|
- `modes` (optional):
|
||||||
|
|
||||||
Configures the input modes.
|
Configures the input modes.
|
||||||
|
|
@ -2672,32 +2452,6 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a [Egui](#types-Egui).
|
The value of this field should be a [Egui](#types-Egui).
|
||||||
|
|
||||||
- `scratchpads` (optional):
|
|
||||||
|
|
||||||
An array of pre-configured scratchpads.
|
|
||||||
|
|
||||||
Each entry launches a program when the graphics are first initialized and
|
|
||||||
immediately parks its window in the named scratchpad. The window is captured
|
|
||||||
via a unique tag attached to the spawned process, so other windows of the
|
|
||||||
same application are never affected.
|
|
||||||
|
|
||||||
Use a `toggle-scratchpad` or `cycle-scratchpad` action to bring the windows
|
|
||||||
up; they are always shown floating.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[scratchpads]]
|
|
||||||
name = "term"
|
|
||||||
exec = "foot"
|
|
||||||
|
|
||||||
[[scratchpads]]
|
|
||||||
name = "notes"
|
|
||||||
exec = ["obsidian"]
|
|
||||||
```
|
|
||||||
|
|
||||||
The value of this field should be an array of [Scratchpads](#types-Scratchpad).
|
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Connector"></a>
|
<a name="types-Connector"></a>
|
||||||
### `Connector`
|
### `Connector`
|
||||||
|
|
@ -4631,40 +4385,6 @@ The table has the following fields:
|
||||||
The value of this field should be a string.
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Scratchpad"></a>
|
|
||||||
### `Scratchpad`
|
|
||||||
|
|
||||||
A pre-configured scratchpad whose program is launched at startup and parked
|
|
||||||
in the scratchpad.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[scratchpads]]
|
|
||||||
name = "term"
|
|
||||||
exec = "foot"
|
|
||||||
```
|
|
||||||
|
|
||||||
Values of this type should be tables.
|
|
||||||
|
|
||||||
The table has the following fields:
|
|
||||||
|
|
||||||
- `name` (required):
|
|
||||||
|
|
||||||
The name of the scratchpad that the spawned window is parked in.
|
|
||||||
|
|
||||||
The value of this field should be a string.
|
|
||||||
|
|
||||||
- `exec` (optional):
|
|
||||||
|
|
||||||
The program to launch when the graphics are first initialized.
|
|
||||||
|
|
||||||
If omitted, no program is launched and the scratchpad is only created on
|
|
||||||
demand by `send-to-scratchpad`.
|
|
||||||
|
|
||||||
The value of this field should be a [Exec](#types-Exec).
|
|
||||||
|
|
||||||
|
|
||||||
<a name="types-SimpleActionName"></a>
|
<a name="types-SimpleActionName"></a>
|
||||||
### `SimpleActionName`
|
### `SimpleActionName`
|
||||||
|
|
||||||
|
|
@ -4756,18 +4476,6 @@ The string should have one of the following values:
|
||||||
|
|
||||||
Toggles the current group between tabbed and split mode.
|
Toggles the current group between tabbed and split mode.
|
||||||
|
|
||||||
- `enable-autotile`:
|
|
||||||
|
|
||||||
Enables alternating split orientation for newly tiled windows.
|
|
||||||
|
|
||||||
- `disable-autotile`:
|
|
||||||
|
|
||||||
Disables alternating split orientation for newly tiled windows.
|
|
||||||
|
|
||||||
- `toggle-autotile`:
|
|
||||||
|
|
||||||
Toggles alternating split orientation for newly tiled windows.
|
|
||||||
|
|
||||||
- `toggle-fullscreen`:
|
- `toggle-fullscreen`:
|
||||||
|
|
||||||
Toggle the currently focused window between fullscreen and windowed.
|
Toggle the currently focused window between fullscreen and windowed.
|
||||||
|
|
@ -4780,18 +4488,6 @@ The string should have one of the following values:
|
||||||
|
|
||||||
Makes the currently focused window windowed.
|
Makes the currently focused window windowed.
|
||||||
|
|
||||||
- `send-to-scratchpad`:
|
|
||||||
|
|
||||||
Sends the currently focused window to the default scratchpad.
|
|
||||||
|
|
||||||
- `toggle-scratchpad`:
|
|
||||||
|
|
||||||
Toggles the default scratchpad.
|
|
||||||
|
|
||||||
- `cycle-scratchpad`:
|
|
||||||
|
|
||||||
Cycles through the windows of the default scratchpad.
|
|
||||||
|
|
||||||
- `focus-parent`:
|
- `focus-parent`:
|
||||||
|
|
||||||
Focus the parent of the currently focused window.
|
Focus the parent of the currently focused window.
|
||||||
|
|
|
||||||
|
|
@ -345,64 +345,6 @@ Action:
|
||||||
description: The name of the workspace.
|
description: The name of the workspace.
|
||||||
required: true
|
required: true
|
||||||
kind: string
|
kind: string
|
||||||
send-to-scratchpad:
|
|
||||||
description: |
|
|
||||||
Sends the currently focused window to a scratchpad and hides it.
|
|
||||||
|
|
||||||
A scratchpad can hold any number of windows. If `name` is omitted, the
|
|
||||||
default scratchpad is used.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[shortcuts]
|
|
||||||
alt-shift-minus = { type = "send-to-scratchpad", name = "terminal" }
|
|
||||||
```
|
|
||||||
fields:
|
|
||||||
name:
|
|
||||||
description: The name of the scratchpad.
|
|
||||||
required: false
|
|
||||||
kind: string
|
|
||||||
toggle-scratchpad:
|
|
||||||
description: |
|
|
||||||
Toggles a scratchpad.
|
|
||||||
|
|
||||||
If the scratchpad has a visible window, that window is hidden. Otherwise, the
|
|
||||||
most recently hidden window in the scratchpad is shown on the current workspace.
|
|
||||||
Only one window of a scratchpad is shown at a time, and scratchpad windows are
|
|
||||||
always shown floating. If `name` is omitted, the default scratchpad is used.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[shortcuts]
|
|
||||||
alt-minus = { type = "toggle-scratchpad", name = "terminal" }
|
|
||||||
```
|
|
||||||
fields:
|
|
||||||
name:
|
|
||||||
description: The name of the scratchpad.
|
|
||||||
required: false
|
|
||||||
kind: string
|
|
||||||
cycle-scratchpad:
|
|
||||||
description: |
|
|
||||||
Cycles through the windows of a scratchpad, one at a time.
|
|
||||||
|
|
||||||
With no window shown, the first window is brought up. Each further invocation
|
|
||||||
hides the current window and shows the next; after the last window the
|
|
||||||
scratchpad is hidden again. Scratchpad windows are always shown floating.
|
|
||||||
If `name` is omitted, the default scratchpad is used.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[shortcuts]
|
|
||||||
alt-minus = { type = "cycle-scratchpad", name = "terminal" }
|
|
||||||
```
|
|
||||||
fields:
|
|
||||||
name:
|
|
||||||
description: The name of the scratchpad.
|
|
||||||
required: false
|
|
||||||
kind: string
|
|
||||||
move-to-output:
|
move-to-output:
|
||||||
description: |
|
description: |
|
||||||
Moves a workspace to a different output.
|
Moves a workspace to a different output.
|
||||||
|
|
@ -1122,24 +1064,12 @@ SimpleActionName:
|
||||||
description: Toggles the current group's direction.
|
description: Toggles the current group's direction.
|
||||||
- value: toggle-tab
|
- value: toggle-tab
|
||||||
description: Toggles the current group between tabbed and split mode.
|
description: Toggles the current group between tabbed and split mode.
|
||||||
- value: enable-autotile
|
|
||||||
description: Enables alternating split orientation for newly tiled windows.
|
|
||||||
- value: disable-autotile
|
|
||||||
description: Disables alternating split orientation for newly tiled windows.
|
|
||||||
- value: toggle-autotile
|
|
||||||
description: Toggles alternating split orientation for newly tiled windows.
|
|
||||||
- value: toggle-fullscreen
|
- value: toggle-fullscreen
|
||||||
description: Toggle the currently focused window between fullscreen and windowed.
|
description: Toggle the currently focused window between fullscreen and windowed.
|
||||||
- value: enter-fullscreen
|
- value: enter-fullscreen
|
||||||
description: Makes the currently focused window fullscreen.
|
description: Makes the currently focused window fullscreen.
|
||||||
- value: exit-fullscreen
|
- value: exit-fullscreen
|
||||||
description: Makes the currently focused window windowed.
|
description: Makes the currently focused window windowed.
|
||||||
- value: send-to-scratchpad
|
|
||||||
description: Sends the currently focused window to the default scratchpad.
|
|
||||||
- value: toggle-scratchpad
|
|
||||||
description: Toggles the default scratchpad.
|
|
||||||
- value: cycle-scratchpad
|
|
||||||
description: Cycles through the windows of the default scratchpad.
|
|
||||||
- value: focus-parent
|
- value: focus-parent
|
||||||
description: Focus the parent of the currently focused window.
|
description: Focus the parent of the currently focused window.
|
||||||
- value: close
|
- value: close
|
||||||
|
|
@ -3012,23 +2942,6 @@ Config:
|
||||||
```toml
|
```toml
|
||||||
ui-drag = { enabled = false, threshold = 20 }
|
ui-drag = { enabled = false, threshold = 20 }
|
||||||
```
|
```
|
||||||
animations:
|
|
||||||
ref: Animations
|
|
||||||
required: false
|
|
||||||
description: |
|
|
||||||
Configures window animations.
|
|
||||||
|
|
||||||
Animations are disabled by default.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[animations]
|
|
||||||
enabled = true
|
|
||||||
duration-ms = 160
|
|
||||||
style = "multiphase"
|
|
||||||
curve = "ease-out"
|
|
||||||
```
|
|
||||||
xwayland:
|
xwayland:
|
||||||
ref: Xwayland
|
ref: Xwayland
|
||||||
required: false
|
required: false
|
||||||
|
|
@ -3199,21 +3112,10 @@ Config:
|
||||||
required: false
|
required: false
|
||||||
description: |
|
description: |
|
||||||
Configures whether middle-click pasting is enabled.
|
Configures whether middle-click pasting is enabled.
|
||||||
|
|
||||||
Changing this has no effect on running applications.
|
Changing this has no effect on running applications.
|
||||||
|
|
||||||
The default is `true`.
|
The default is `true`.
|
||||||
autotile:
|
|
||||||
kind: boolean
|
|
||||||
required: false
|
|
||||||
description: |
|
|
||||||
Configures whether autotiling is enabled by default.
|
|
||||||
|
|
||||||
When enabled, newly mapped tiled windows alternate their split
|
|
||||||
orientation automatically. This can also be toggled at runtime via the
|
|
||||||
`enable-autotile`, `disable-autotile`, and `toggle-autotile` actions.
|
|
||||||
|
|
||||||
The default is `false`.
|
|
||||||
modes:
|
modes:
|
||||||
kind: map
|
kind: map
|
||||||
values:
|
values:
|
||||||
|
|
@ -3310,61 +3212,6 @@ Config:
|
||||||
required: false
|
required: false
|
||||||
description: |
|
description: |
|
||||||
Sets the egui settings of the compositor.
|
Sets the egui settings of the compositor.
|
||||||
scratchpads:
|
|
||||||
kind: array
|
|
||||||
items:
|
|
||||||
ref: Scratchpad
|
|
||||||
required: false
|
|
||||||
description: |
|
|
||||||
An array of pre-configured scratchpads.
|
|
||||||
|
|
||||||
Each entry launches a program when the graphics are first initialized and
|
|
||||||
immediately parks its window in the named scratchpad. The window is captured
|
|
||||||
via a unique tag attached to the spawned process, so other windows of the
|
|
||||||
same application are never affected.
|
|
||||||
|
|
||||||
Use a `toggle-scratchpad` or `cycle-scratchpad` action to bring the windows
|
|
||||||
up; they are always shown floating.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[scratchpads]]
|
|
||||||
name = "term"
|
|
||||||
exec = "foot"
|
|
||||||
|
|
||||||
[[scratchpads]]
|
|
||||||
name = "notes"
|
|
||||||
exec = ["obsidian"]
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Scratchpad:
|
|
||||||
kind: table
|
|
||||||
description: |
|
|
||||||
A pre-configured scratchpad whose program is launched at startup and parked
|
|
||||||
in the scratchpad.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[scratchpads]]
|
|
||||||
name = "term"
|
|
||||||
exec = "foot"
|
|
||||||
```
|
|
||||||
fields:
|
|
||||||
name:
|
|
||||||
kind: string
|
|
||||||
required: true
|
|
||||||
description: The name of the scratchpad that the spawned window is parked in.
|
|
||||||
exec:
|
|
||||||
ref: Exec
|
|
||||||
required: false
|
|
||||||
description: |
|
|
||||||
The program to launch when the graphics are first initialized.
|
|
||||||
|
|
||||||
If omitted, no program is launched and the scratchpad is only created on
|
|
||||||
demand by `send-to-scratchpad`.
|
|
||||||
|
|
||||||
|
|
||||||
Idle:
|
Idle:
|
||||||
|
|
@ -3808,97 +3655,6 @@ UiDrag:
|
||||||
The default is `10`.
|
The default is `10`.
|
||||||
|
|
||||||
|
|
||||||
Animations:
|
|
||||||
kind: table
|
|
||||||
description: |
|
|
||||||
Describes window animation settings.
|
|
||||||
|
|
||||||
- Example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[animations]
|
|
||||||
enabled = true
|
|
||||||
duration-ms = 160
|
|
||||||
style = "multiphase"
|
|
||||||
curve = [0.25, 0.1, 0.25, 1.0]
|
|
||||||
```
|
|
||||||
fields:
|
|
||||||
enabled:
|
|
||||||
kind: boolean
|
|
||||||
required: false
|
|
||||||
description: |
|
|
||||||
Enables or disables window animations.
|
|
||||||
|
|
||||||
The default is `false`.
|
|
||||||
duration-ms:
|
|
||||||
kind: number
|
|
||||||
integer_only: true
|
|
||||||
required: false
|
|
||||||
description: |
|
|
||||||
Sets the animation duration in milliseconds.
|
|
||||||
|
|
||||||
The default is `160`.
|
|
||||||
style:
|
|
||||||
ref: AnimationStyle
|
|
||||||
required: false
|
|
||||||
description: |
|
|
||||||
Sets the animation style used for tiled window movement animations.
|
|
||||||
|
|
||||||
The default is `multiphase`.
|
|
||||||
curve:
|
|
||||||
ref: AnimationCurve
|
|
||||||
required: false
|
|
||||||
description: |
|
|
||||||
Sets the animation curve.
|
|
||||||
|
|
||||||
The default is `ease-out`.
|
|
||||||
|
|
||||||
|
|
||||||
AnimationStyle:
|
|
||||||
kind: string
|
|
||||||
description: |
|
|
||||||
Describes a tiled window movement animation style.
|
|
||||||
values:
|
|
||||||
- value: plain
|
|
||||||
description: |
|
|
||||||
Uses a single interpolated movement from each window's current visual
|
|
||||||
rectangle to its destination rectangle.
|
|
||||||
- value: multiphase
|
|
||||||
description: |
|
|
||||||
Uses the no-overlap multiphase planner for tiled window movement when a
|
|
||||||
supported plan exists.
|
|
||||||
|
|
||||||
|
|
||||||
AnimationCurve:
|
|
||||||
kind: variable
|
|
||||||
description: |
|
|
||||||
Describes a window animation curve.
|
|
||||||
variants:
|
|
||||||
- kind: string
|
|
||||||
description: |
|
|
||||||
One of the supported curve presets.
|
|
||||||
values:
|
|
||||||
- value: linear
|
|
||||||
description: No easing.
|
|
||||||
- value: ease
|
|
||||||
description: The CSS `ease` curve.
|
|
||||||
- value: ease-in
|
|
||||||
description: The CSS `ease-in` curve.
|
|
||||||
- value: ease-out
|
|
||||||
description: The CSS `ease-out` curve.
|
|
||||||
- value: ease-in-out
|
|
||||||
description: The CSS `ease-in-out` curve.
|
|
||||||
- kind: array
|
|
||||||
items:
|
|
||||||
kind: number
|
|
||||||
description: |
|
|
||||||
A custom CSS-style cubic-bezier curve as four numbers:
|
|
||||||
`x1`, `y1`, `x2`, and `y2`.
|
|
||||||
|
|
||||||
The implicit endpoints are `(0, 0)` and `(1, 1)`. `x1` and `x2` must
|
|
||||||
be between `0` and `1`.
|
|
||||||
|
|
||||||
|
|
||||||
Xwayland:
|
Xwayland:
|
||||||
kind: table
|
kind: table
|
||||||
description: |
|
description: |
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue