Merge pull request #440 from mahkoh/jorth/pin-float
tree: allow floats to be pinned
This commit is contained in:
commit
8552c5f1eb
33 changed files with 917 additions and 36 deletions
44
Cargo.lock
generated
44
Cargo.lock
generated
|
|
@ -110,6 +110,12 @@ version = "1.0.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
|
@ -183,6 +189,12 @@ version = "3.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
|
@ -597,6 +609,7 @@ dependencies = [
|
||||||
"shaderc",
|
"shaderc",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tiny-skia",
|
||||||
"tracy-client-sys",
|
"tracy-client-sys",
|
||||||
"uapi",
|
"uapi",
|
||||||
]
|
]
|
||||||
|
|
@ -1245,6 +1258,12 @@ version = "1.13.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strict-num"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
|
@ -1345,6 +1364,31 @@ dependencies = [
|
||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"bytemuck",
|
||||||
|
"cfg-if",
|
||||||
|
"log",
|
||||||
|
"tiny-skia-path",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia-path"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"bytemuck",
|
||||||
|
"strict-num",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ png = "0.17.13"
|
||||||
rustc-demangle = { version = "0.1.24", optional = true }
|
rustc-demangle = { version = "0.1.24", optional = true }
|
||||||
tracy-client-sys = { version = "0.24.1", features = ["ondemand", "manual-lifetime", "debuginfod", "demangle"], optional = true }
|
tracy-client-sys = { version = "0.24.1", features = ["ondemand", "manual-lifetime", "debuginfod", "demangle"], optional = true }
|
||||||
kbvm = "0.1.4"
|
kbvm = "0.1.4"
|
||||||
|
tiny-skia = { version = "0.11.4", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
repc = "0.1.1"
|
repc = "0.1.1"
|
||||||
|
|
|
||||||
|
|
@ -778,6 +778,20 @@ impl Client {
|
||||||
above
|
above
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_show_float_pin_icon(&self, show: bool) {
|
||||||
|
self.send(&ClientMessage::SetShowFloatPinIcon { show });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pinned(&self, seat: Seat) -> bool {
|
||||||
|
let res = self.send_with_response(&ClientMessage::GetFloatPinned { seat });
|
||||||
|
get_response!(res, false, GetFloatPinned { pinned });
|
||||||
|
pinned
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pinned(&self, seat: Seat, pinned: bool) {
|
||||||
|
self.send(&ClientMessage::SetFloatPinned { seat, pinned });
|
||||||
|
}
|
||||||
|
|
||||||
pub fn connector_connected(&self, connector: Connector) -> bool {
|
pub fn connector_connected(&self, connector: Connector) -> bool {
|
||||||
let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector });
|
let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector });
|
||||||
get_response!(res, false, ConnectorConnected { connected });
|
get_response!(res, false, ConnectorConnected { connected });
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,16 @@ pub enum ClientMessage<'a> {
|
||||||
above: bool,
|
above: bool,
|
||||||
},
|
},
|
||||||
GetFloatAboveFullscreen,
|
GetFloatAboveFullscreen,
|
||||||
|
GetFloatPinned {
|
||||||
|
seat: Seat,
|
||||||
|
},
|
||||||
|
SetFloatPinned {
|
||||||
|
seat: Seat,
|
||||||
|
pinned: bool,
|
||||||
|
},
|
||||||
|
SetShowFloatPinIcon {
|
||||||
|
show: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
@ -697,6 +707,9 @@ pub enum Response {
|
||||||
GetFloatAboveFullscreen {
|
GetFloatAboveFullscreen {
|
||||||
above: bool,
|
above: bool,
|
||||||
},
|
},
|
||||||
|
GetFloatPinned {
|
||||||
|
pinned: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -452,6 +452,24 @@ impl Seat {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets whether the currently focused window is pinned.
|
||||||
|
///
|
||||||
|
/// If a floating window is pinned, it will stay visible even when switching to a
|
||||||
|
/// different workspace.
|
||||||
|
pub fn float_pinned(self) -> bool {
|
||||||
|
get!().get_pinned(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether the currently focused window is pinned.
|
||||||
|
pub fn set_float_pinned(self, pinned: bool) {
|
||||||
|
get!().set_pinned(self, pinned);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggles whether the currently focused window is pinned.
|
||||||
|
pub fn toggle_float_pinned(self) {
|
||||||
|
self.set_float_pinned(!self.float_pinned());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A focus-follows-mouse mode.
|
/// A focus-follows-mouse mode.
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@
|
||||||
)]
|
)]
|
||||||
#![warn(unsafe_op_in_unsafe_fn)]
|
#![warn(unsafe_op_in_unsafe_fn)]
|
||||||
|
|
||||||
|
#[expect(unused_imports)]
|
||||||
|
use crate::input::Seat;
|
||||||
use {
|
use {
|
||||||
crate::{_private::ipc::WorkspaceSource, keyboard::ModifiedKeySym, video::Connector},
|
crate::{_private::ipc::WorkspaceSource, keyboard::ModifiedKeySym, video::Connector},
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
|
|
@ -292,3 +294,13 @@ pub fn get_float_above_fullscreen() -> bool {
|
||||||
pub fn toggle_float_above_fullscreen() {
|
pub fn toggle_float_above_fullscreen() {
|
||||||
set_float_above_fullscreen(!get_float_above_fullscreen())
|
set_float_above_fullscreen(!get_float_above_fullscreen())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether floating windows always show a pin icon.
|
||||||
|
///
|
||||||
|
/// Clicking on the pin icon toggles the pin mode. See [`Seat::toggle_float_pinned`].
|
||||||
|
///
|
||||||
|
/// The icon is always shown if the window is pinned. This setting only affects unpinned
|
||||||
|
/// windows.
|
||||||
|
pub fn set_show_float_pin_icon(show: bool) {
|
||||||
|
get!().set_show_float_pin_icon(show);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
by using the `enable-float-above-fullscreen` action.
|
by using the `enable-float-above-fullscreen` action.
|
||||||
- Implement xdg-toplevel-tag-v1.
|
- Implement xdg-toplevel-tag-v1.
|
||||||
- Implement tablet-v2 version 2.
|
- Implement tablet-v2 version 2.
|
||||||
|
- Floating windows can now be pinned. A pinned floating window stays visible on
|
||||||
|
its output even when switching workspaces.
|
||||||
|
|
||||||
# 1.10.0 (2025-04-22)
|
# 1.10.0 (2025-04-22)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -288,6 +288,8 @@ fn start_compositor2(
|
||||||
color_management_enabled: Cell::new(false),
|
color_management_enabled: Cell::new(false),
|
||||||
color_manager,
|
color_manager,
|
||||||
float_above_fullscreen: Cell::new(false),
|
float_above_fullscreen: Cell::new(false),
|
||||||
|
icons: Default::default(),
|
||||||
|
show_pin_icon: Cell::new(false),
|
||||||
});
|
});
|
||||||
state.tracker.register(ClientId::from_raw(0));
|
state.tracker.register(ClientId::from_raw(0));
|
||||||
create_dummy_output(&state);
|
create_dummy_output(&state);
|
||||||
|
|
@ -617,6 +619,7 @@ fn create_dummy_output(state: &Rc<State>) {
|
||||||
tray_start_rel: Default::default(),
|
tray_start_rel: Default::default(),
|
||||||
tray_items: Default::default(),
|
tray_items: Default::default(),
|
||||||
ext_workspace_groups: Default::default(),
|
ext_workspace_groups: Default::default(),
|
||||||
|
pinned: Default::default(),
|
||||||
});
|
});
|
||||||
let dummy_workspace = Rc::new(WorkspaceNode {
|
let dummy_workspace = Rc::new(WorkspaceNode {
|
||||||
id: state.node_ids.next(),
|
id: state.node_ids.next(),
|
||||||
|
|
|
||||||
|
|
@ -1151,6 +1151,29 @@ impl ConfigProxyHandler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_set_show_float_pin_icon(&self, show: bool) {
|
||||||
|
self.state.show_pin_icon.set(show);
|
||||||
|
for stacked in self.state.root.stacked.iter() {
|
||||||
|
if let Some(float) = stacked.deref().clone().node_into_float() {
|
||||||
|
float.schedule_render_titles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_get_float_pinned(&self, seat: Seat) -> Result<(), CphError> {
|
||||||
|
let seat = self.get_seat(seat)?;
|
||||||
|
self.respond(Response::GetFloatPinned {
|
||||||
|
pinned: seat.pinned(),
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_set_float_pinned(&self, seat: Seat, pinned: bool) -> Result<(), CphError> {
|
||||||
|
let seat = self.get_seat(seat)?;
|
||||||
|
seat.set_pinned(pinned);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_set_vrr_mode(
|
fn handle_set_vrr_mode(
|
||||||
&self,
|
&self,
|
||||||
connector: Option<Connector>,
|
connector: Option<Connector>,
|
||||||
|
|
@ -1545,6 +1568,7 @@ impl ConfigProxyHandler {
|
||||||
}
|
}
|
||||||
self.state.root.clone().node_visit(&mut V);
|
self.state.root.clone().node_visit(&mut V);
|
||||||
self.state.damage(self.state.root.extents.get());
|
self.state.damage(self.state.root.extents.get());
|
||||||
|
self.state.icons.update_sizes(&self.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn colors_changed(&self) {
|
fn colors_changed(&self) {
|
||||||
|
|
@ -1561,6 +1585,7 @@ impl ConfigProxyHandler {
|
||||||
}
|
}
|
||||||
self.state.root.clone().node_visit(&mut V);
|
self.state.root.clone().node_visit(&mut V);
|
||||||
self.state.damage(self.state.root.extents.get());
|
self.state.damage(self.state.root.extents.get());
|
||||||
|
self.state.icons.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sized(&self, sized: Resizable) -> Result<ThemeSized, CphError> {
|
fn get_sized(&self, sized: Resizable) -> Result<ThemeSized, CphError> {
|
||||||
|
|
@ -2058,6 +2083,15 @@ impl ConfigProxyHandler {
|
||||||
self.handle_set_float_above_fullscreen(above)
|
self.handle_set_float_above_fullscreen(above)
|
||||||
}
|
}
|
||||||
ClientMessage::GetFloatAboveFullscreen => self.handle_get_float_above_fullscreen(),
|
ClientMessage::GetFloatAboveFullscreen => self.handle_get_float_above_fullscreen(),
|
||||||
|
ClientMessage::GetFloatPinned { seat } => {
|
||||||
|
self.handle_get_float_pinned(seat).wrn("get_float_pinned")?
|
||||||
|
}
|
||||||
|
ClientMessage::SetFloatPinned { seat, pinned } => self
|
||||||
|
.handle_set_float_pinned(seat, pinned)
|
||||||
|
.wrn("set_float_pinned")?,
|
||||||
|
ClientMessage::SetShowFloatPinIcon { show } => {
|
||||||
|
self.handle_set_show_float_pin_icon(show)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -618,6 +618,7 @@ impl dyn GfxFramebuffer {
|
||||||
let (width, height) = self.logical_size(transform);
|
let (width, height) = self.logical_size(transform);
|
||||||
Rect::new(0, 0, width, height).unwrap()
|
Rect::new(0, 0, width, height).unwrap()
|
||||||
},
|
},
|
||||||
|
icons: None,
|
||||||
};
|
};
|
||||||
cursor.render_hardware_cursor(&mut renderer);
|
cursor.render_hardware_cursor(&mut renderer);
|
||||||
self.render(
|
self.render(
|
||||||
|
|
@ -906,6 +907,7 @@ pub fn create_render_pass(
|
||||||
let (width, height) = logical_size(physical_size, transform);
|
let (width, height) = logical_size(physical_size, transform);
|
||||||
Rect::new(0, 0, width, height).unwrap()
|
Rect::new(0, 0, width, height).unwrap()
|
||||||
},
|
},
|
||||||
|
icons: state.icons.get(state, scale),
|
||||||
};
|
};
|
||||||
node.node_render(&mut renderer, 0, 0, None);
|
node.node_render(&mut renderer, 0, 0, None);
|
||||||
if let Some(rect) = cursor_rect {
|
if let Some(rect) = cursor_rect {
|
||||||
|
|
|
||||||
283
src/icons.rs
Normal file
283
src/icons.rs
Normal file
|
|
@ -0,0 +1,283 @@
|
||||||
|
#![allow(clippy::excessive_precision)]
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
cmm::cmm_transfer_function::TransferFunction,
|
||||||
|
format::ARGB8888,
|
||||||
|
gfx_api::{GfxContext, GfxError, GfxTexture},
|
||||||
|
scale::Scale,
|
||||||
|
state::State,
|
||||||
|
theme::Theme,
|
||||||
|
utils::{copyhashmap::CopyHashMap, windows::WindowsExt},
|
||||||
|
},
|
||||||
|
ahash::AHashSet,
|
||||||
|
linearize::{Linearize, StaticMap, static_map},
|
||||||
|
std::{cell::Cell, f32::consts::PI, mem, rc::Rc, sync::LazyLock},
|
||||||
|
thiserror::Error,
|
||||||
|
tiny_skia::{Color, FillRule, Paint, Path, PathBuilder, Pixmap, Transform},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Icons {
|
||||||
|
icons: CopyHashMap<i32, Option<Rc<SizedIcons>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Linearize)]
|
||||||
|
pub enum IconState {
|
||||||
|
Active,
|
||||||
|
Passive,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SizedIcons {
|
||||||
|
pub pin_unfocused_title: StaticMap<IconState, Rc<dyn GfxTexture>>,
|
||||||
|
pub pin_focused_title: StaticMap<IconState, Rc<dyn GfxTexture>>,
|
||||||
|
pub pin_attention_requested: StaticMap<IconState, Rc<dyn GfxTexture>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum IconsError {
|
||||||
|
#[error("Could not create a pixmap")]
|
||||||
|
CreatePixmap,
|
||||||
|
#[error("The requested icons size is non-positive")]
|
||||||
|
NonPositiveSize,
|
||||||
|
#[error("There is no gfx context")]
|
||||||
|
NoRenderContext,
|
||||||
|
#[error("Could not create texture")]
|
||||||
|
CreateTexture(#[source] GfxError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Icons {
|
||||||
|
pub fn update_sizes(&self, state: &State) {
|
||||||
|
let mut sizes = AHashSet::new();
|
||||||
|
let height = state.theme.sizes.title_height.get();
|
||||||
|
for &(scale, _) in &*state.scales.lock() {
|
||||||
|
let [size] = scale.pixel_size([height]);
|
||||||
|
if size > 0 {
|
||||||
|
sizes.insert(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.icons.lock().retain(|size, _| sizes.contains(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&self) {
|
||||||
|
self.icons.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, state: &State, scale: Scale) -> Option<Rc<SizedIcons>> {
|
||||||
|
let [size] = scale.pixel_size([state.theme.sizes.title_height.get()]);
|
||||||
|
if let Some(icons) = self.icons.get(&size) {
|
||||||
|
return icons;
|
||||||
|
}
|
||||||
|
let icons = match self.create(state, size) {
|
||||||
|
Ok(i) => Some(i),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Could not create icons: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.icons.set(size, icons.clone());
|
||||||
|
icons
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(&self, state: &State, size: i32) -> Result<Rc<SizedIcons>, IconsError> {
|
||||||
|
let Some(ctx) = state.render_ctx.get() else {
|
||||||
|
return Err(IconsError::NoRenderContext);
|
||||||
|
};
|
||||||
|
Ok(Rc::new(create_icons(size, &state.theme, &ctx)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_icons(
|
||||||
|
size: i32,
|
||||||
|
theme: &Theme,
|
||||||
|
ctx: &Rc<dyn GfxContext>,
|
||||||
|
) -> Result<SizedIcons, IconsError> {
|
||||||
|
if size <= 0 {
|
||||||
|
return Err(IconsError::NonPositiveSize);
|
||||||
|
}
|
||||||
|
let size = size as u32;
|
||||||
|
|
||||||
|
let create_pins = |color: crate::theme::Color| {
|
||||||
|
let create_pin = |color: Color| {
|
||||||
|
let mut paint = Paint::default();
|
||||||
|
paint.set_color(color);
|
||||||
|
let s = size as f32 / 100.0;
|
||||||
|
let transform = Transform::from_scale(s, s);
|
||||||
|
let mut pixmap = Pixmap::new(size, size).ok_or(IconsError::CreatePixmap)?;
|
||||||
|
pixmap.fill_path(&PIN_PATH, &paint, FillRule::EvenOdd, transform, None);
|
||||||
|
upload_pixmap(pixmap, ctx)
|
||||||
|
};
|
||||||
|
let colors = calculate_accents(color);
|
||||||
|
Ok(static_map! {
|
||||||
|
IconState::Passive => create_pin(colors[0])?,
|
||||||
|
IconState::Active => create_pin(colors[1])?,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(SizedIcons {
|
||||||
|
pin_unfocused_title: create_pins(theme.colors.unfocused_title_background.get())?,
|
||||||
|
pin_focused_title: create_pins(theme.colors.focused_title_background.get())?,
|
||||||
|
pin_attention_requested: create_pins(theme.colors.attention_requested_background.get())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload_pixmap(
|
||||||
|
pixmap: Pixmap,
|
||||||
|
ctx: &Rc<dyn GfxContext>,
|
||||||
|
) -> Result<Rc<dyn GfxTexture>, IconsError> {
|
||||||
|
let width = pixmap.width();
|
||||||
|
let height = pixmap.width();
|
||||||
|
let bytes = unsafe { mem::transmute::<Vec<u8>, Vec<Cell<u8>>>(pixmap.take()) };
|
||||||
|
for chunk in bytes.array_chunks_ext::<4>() {
|
||||||
|
let r = chunk[0].get();
|
||||||
|
let b = chunk[2].get();
|
||||||
|
chunk[0].set(b);
|
||||||
|
chunk[2].set(r);
|
||||||
|
}
|
||||||
|
let tex: Rc<dyn GfxTexture> = ctx
|
||||||
|
.clone()
|
||||||
|
.shmem_texture(
|
||||||
|
None,
|
||||||
|
&bytes,
|
||||||
|
ARGB8888,
|
||||||
|
width as _,
|
||||||
|
height as _,
|
||||||
|
width as i32 * 4,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.map_err(IconsError::CreateTexture)?;
|
||||||
|
Ok(tex)
|
||||||
|
}
|
||||||
|
|
||||||
|
static PIN_PATH: LazyLock<Path> = LazyLock::new(|| {
|
||||||
|
let cx = 50.0f32;
|
||||||
|
let cy = 40.0f32;
|
||||||
|
let r = 30.0f32;
|
||||||
|
let xx = cx;
|
||||||
|
let xy = 90.0f32;
|
||||||
|
let d = xy - cy;
|
||||||
|
let v1 = r / d * (d * d - r * r).sqrt();
|
||||||
|
let v2 = 1.0 / d * (d * d - r * r);
|
||||||
|
|
||||||
|
let mut path = PathBuilder::new();
|
||||||
|
path.move_to(cx, cy - r);
|
||||||
|
path.arc_cw_to(cx, cy, cx + r, cy);
|
||||||
|
path.arc_cw_to(cx, cy, xx + v1, xy - v2);
|
||||||
|
path.line_to(xx, xy);
|
||||||
|
path.line_to(xx - v1, xy - v2);
|
||||||
|
path.arc_cw_to(cx, cy, cx - r, cy);
|
||||||
|
path.arc_cw_to(cx, cy, cx, cy - r);
|
||||||
|
path.close();
|
||||||
|
path.push_circle(cx, cy, r / 2.5);
|
||||||
|
path.finish().unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pin_path() {
|
||||||
|
let _path = &*PIN_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait PathBuilderExt {
|
||||||
|
fn arc_cw_to(&mut self, cx: f32, cy: f32, x: f32, y: f32);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathBuilderExt for PathBuilder {
|
||||||
|
fn arc_cw_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
|
||||||
|
let (x0, y0) = match self.last_point() {
|
||||||
|
None => {
|
||||||
|
self.move_to(0.0, 0.0);
|
||||||
|
(0.0, 0.0)
|
||||||
|
}
|
||||||
|
Some(p) => (p.x, p.y),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ux = x0 - cx;
|
||||||
|
let uy = y0 - cy;
|
||||||
|
let ul = (ux * ux + uy * uy).sqrt();
|
||||||
|
let uxn = ux / ul;
|
||||||
|
let uyn = uy / ul;
|
||||||
|
let a1 = (uy / ux).atan();
|
||||||
|
|
||||||
|
let tx = x - cx;
|
||||||
|
let ty = y - cy;
|
||||||
|
let tl = (tx * tx + ty * ty).sqrt();
|
||||||
|
let txn = tx / tl;
|
||||||
|
let tyn = ty / tl;
|
||||||
|
let a2 = (ty / tx).atan();
|
||||||
|
|
||||||
|
let c = 4.0 / 3.0 * ((a2 - a1 + PI) % PI / 4.0).tan();
|
||||||
|
let uc = ul * c;
|
||||||
|
let tc = tl * c;
|
||||||
|
self.cubic_to(
|
||||||
|
x0 - uyn * uc,
|
||||||
|
y0 + uxn * uc,
|
||||||
|
x + tyn * tc,
|
||||||
|
y - txn * tc,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::theme::Color> for Color {
|
||||||
|
fn from(v: crate::theme::Color) -> Self {
|
||||||
|
let [r, g, b, a] = v.to_array(TransferFunction::Srgb);
|
||||||
|
let mut c = Self::TRANSPARENT;
|
||||||
|
c.set_red(r / a);
|
||||||
|
c.set_green(g / a);
|
||||||
|
c.set_blue(b / a);
|
||||||
|
c.set_alpha(a);
|
||||||
|
c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_accents(srgb: crate::theme::Color) -> [Color; 2] {
|
||||||
|
let [l, a, b, alpha] = srgb_to_lab(srgb);
|
||||||
|
let l2 = if l < 0.65 { 0.9 } else { l - 0.4 };
|
||||||
|
let l1 = (l2 + l) / 2.0;
|
||||||
|
[
|
||||||
|
lab_to_color([l1, a, b, alpha]),
|
||||||
|
lab_to_color([l2, a, b, alpha]),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn srgb_to_lab(srgb: crate::theme::Color) -> [f32; 4] {
|
||||||
|
let [mut r, mut g, mut b, alpha] = srgb.to_array(TransferFunction::Srgb);
|
||||||
|
if alpha < 1.0 {
|
||||||
|
r /= alpha;
|
||||||
|
g /= alpha;
|
||||||
|
b /= alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
||||||
|
let m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
||||||
|
let s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
||||||
|
|
||||||
|
let l_ = l.cbrt();
|
||||||
|
let m_ = m.cbrt();
|
||||||
|
let s_ = s.cbrt();
|
||||||
|
|
||||||
|
[
|
||||||
|
0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
|
||||||
|
1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
|
||||||
|
0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
|
||||||
|
alpha,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lab_to_color([l, a, b, alpha]: [f32; 4]) -> Color {
|
||||||
|
let l_ = l + 0.3963377774 * a + 0.2158037573 * b;
|
||||||
|
let m_ = l - 0.1055613458 * a - 0.0638541728 * b;
|
||||||
|
let s_ = l - 0.0894841775 * a - 1.2914855480 * b;
|
||||||
|
|
||||||
|
let l = l_ * l_ * l_;
|
||||||
|
let m = m_ * m_ * m_;
|
||||||
|
let s = s_ * s_ * s_;
|
||||||
|
|
||||||
|
let mut c = Color::TRANSPARENT;
|
||||||
|
c.set_red(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s);
|
||||||
|
c.set_green(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s);
|
||||||
|
c.set_blue(-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s);
|
||||||
|
c.set_alpha(alpha);
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
@ -1119,6 +1119,20 @@ impl WlSeatGlobal {
|
||||||
};
|
};
|
||||||
kb.phy_state.destroy(self.state.now_usec(), self);
|
kb.phy_state.destroy(self.state.now_usec(), self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pinned(&self) -> bool {
|
||||||
|
let Some(tl) = self.keyboard_node.get().node_toplevel() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
tl.tl_pinned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pinned(&self, pinned: bool) {
|
||||||
|
let Some(tl) = self.keyboard_node.get().node_toplevel() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
tl.tl_set_pinned(true, pinned);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorUserOwner for WlSeatGlobal {
|
impl CursorUserOwner for WlSeatGlobal {
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ mod format;
|
||||||
mod gfx_api;
|
mod gfx_api;
|
||||||
mod gfx_apis;
|
mod gfx_apis;
|
||||||
mod globals;
|
mod globals;
|
||||||
|
mod icons;
|
||||||
mod ifs;
|
mod ifs;
|
||||||
mod io_uring;
|
mod io_uring;
|
||||||
#[cfg(feature = "it")]
|
#[cfg(feature = "it")]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
gfx_api::{AcquireSync, GfxApiOpt, ReleaseSync, SampleRect},
|
gfx_api::{AcquireSync, GfxApiOpt, ReleaseSync, SampleRect},
|
||||||
|
icons::{IconState, SizedIcons},
|
||||||
ifs::wl_surface::{
|
ifs::wl_surface::{
|
||||||
SurfaceBuffer, WlSurface,
|
SurfaceBuffer, WlSurface,
|
||||||
x_surface::xwindow::Xwindow,
|
x_surface::xwindow::Xwindow,
|
||||||
|
|
@ -27,6 +28,7 @@ pub struct Renderer<'a> {
|
||||||
pub state: &'a State,
|
pub state: &'a State,
|
||||||
pub logical_extents: Rect,
|
pub logical_extents: Rect,
|
||||||
pub pixel_extents: Rect,
|
pub pixel_extents: Rect,
|
||||||
|
pub icons: Option<Rc<SizedIcons>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer<'_> {
|
impl Renderer<'_> {
|
||||||
|
|
@ -521,11 +523,45 @@ impl Renderer<'_> {
|
||||||
let title_underline =
|
let title_underline =
|
||||||
[Rect::new_sized(x + bw, y + bw + th, pos.width() - 2 * bw, 1).unwrap()];
|
[Rect::new_sized(x + bw, y + bw + th, pos.width() - 2 * bw, 1).unwrap()];
|
||||||
self.base.fill_boxes(&title_underline, &uc, srgb);
|
self.base.fill_boxes(&title_underline, &uc, srgb);
|
||||||
|
let rect = floating.title_rect.get().move_(x, y);
|
||||||
|
let bounds = self.base.scale_rect(rect);
|
||||||
|
let (mut x1, y1) = rect.position();
|
||||||
|
let is_pinned = floating.pinned_link.borrow().is_some();
|
||||||
|
if is_pinned || self.state.show_pin_icon.get() {
|
||||||
|
let (x, y) = self.base.scale_point(x1, y1);
|
||||||
|
if let Some(icons) = &self.icons {
|
||||||
|
let icon = if floating.active.get() {
|
||||||
|
&icons.pin_focused_title
|
||||||
|
} else if floating.attention_requested.get() {
|
||||||
|
&icons.pin_attention_requested
|
||||||
|
} else {
|
||||||
|
&icons.pin_unfocused_title
|
||||||
|
};
|
||||||
|
let state = match is_pinned {
|
||||||
|
true => IconState::Active,
|
||||||
|
false => IconState::Passive,
|
||||||
|
};
|
||||||
|
self.base.render_texture(
|
||||||
|
&icon[state],
|
||||||
|
None,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
self.base.scale,
|
||||||
|
Some(&bounds),
|
||||||
|
None,
|
||||||
|
AcquireSync::None,
|
||||||
|
ReleaseSync::None,
|
||||||
|
false,
|
||||||
|
srgb_srgb,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
x1 += th;
|
||||||
|
}
|
||||||
if let Some(title) = floating.title_textures.borrow().get(&self.base.scale) {
|
if let Some(title) = floating.title_textures.borrow().get(&self.base.scale) {
|
||||||
if let Some(texture) = title.texture() {
|
if let Some(texture) = title.texture() {
|
||||||
let rect = floating.title_rect.get().move_(x, y);
|
let (x, y) = self.base.scale_point(x1, y1);
|
||||||
let bounds = self.base.scale_rect(rect);
|
|
||||||
let (x, y) = self.base.scale_point(rect.x1(), rect.y1());
|
|
||||||
self.base.render_texture(
|
self.base.render_texture(
|
||||||
&texture,
|
&texture,
|
||||||
None,
|
None,
|
||||||
|
|
|
||||||
25
src/state.rs
25
src/state.rs
|
|
@ -33,6 +33,7 @@ use {
|
||||||
},
|
},
|
||||||
gfx_apis::create_gfx_context,
|
gfx_apis::create_gfx_context,
|
||||||
globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal},
|
globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal},
|
||||||
|
icons::Icons,
|
||||||
ifs::{
|
ifs::{
|
||||||
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
||||||
ext_idle_notification_v1::ExtIdleNotificationV1,
|
ext_idle_notification_v1::ExtIdleNotificationV1,
|
||||||
|
|
@ -80,7 +81,7 @@ use {
|
||||||
tree::{
|
tree::{
|
||||||
ContainerNode, ContainerSplit, Direction, DisplayNode, FloatNode, LatchListener, Node,
|
ContainerNode, ContainerSplit, Direction, DisplayNode, FloatNode, LatchListener, Node,
|
||||||
NodeIds, NodeVisitorBase, OutputNode, PlaceholderNode, TearingMode, ToplevelNode,
|
NodeIds, NodeVisitorBase, OutputNode, PlaceholderNode, TearingMode, ToplevelNode,
|
||||||
ToplevelNodeBase, VrrMode, WorkspaceNode,
|
ToplevelNodeBase, VrrMode, WorkspaceNode, generic_node_visitor,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
activation_token::ActivationToken, asyncevent::AsyncEvent, bindings::Bindings,
|
activation_token::ActivationToken, asyncevent::AsyncEvent, bindings::Bindings,
|
||||||
|
|
@ -114,7 +115,7 @@ use {
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
fmt::{Debug, Formatter},
|
fmt::{Debug, Formatter},
|
||||||
mem,
|
mem,
|
||||||
ops::DerefMut,
|
ops::{Deref, DerefMut},
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
|
|
@ -237,6 +238,8 @@ pub struct State {
|
||||||
pub color_management_enabled: Cell<bool>,
|
pub color_management_enabled: Cell<bool>,
|
||||||
pub color_manager: Rc<ColorManager>,
|
pub color_manager: Rc<ColorManager>,
|
||||||
pub float_above_fullscreen: Cell<bool>,
|
pub float_above_fullscreen: Cell<bool>,
|
||||||
|
pub icons: Icons,
|
||||||
|
pub show_pin_icon: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Drop for State {
|
// impl Drop for State {
|
||||||
|
|
@ -466,6 +469,7 @@ impl State {
|
||||||
UpdateTextTexturesVisitor.visit_display(&self.root);
|
UpdateTextTexturesVisitor.visit_display(&self.root);
|
||||||
self.reload_cursors();
|
self.reload_cursors();
|
||||||
self.update_xwayland_wire_scale();
|
self.update_xwayland_wire_scale();
|
||||||
|
self.icons.update_sizes(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_sizes_changed(&self) {
|
fn cursor_sizes_changed(&self) {
|
||||||
|
|
@ -501,6 +505,7 @@ impl State {
|
||||||
self.render_ctx_version.fetch_add(1);
|
self.render_ctx_version.fetch_add(1);
|
||||||
self.cursors.set(None);
|
self.cursors.set(None);
|
||||||
self.drm_feedback.set(None);
|
self.drm_feedback.set(None);
|
||||||
|
self.icons.clear();
|
||||||
self.wait_for_sync_obj
|
self.wait_for_sync_obj
|
||||||
.set_ctx(ctx.as_ref().and_then(|c| c.sync_obj_ctx().cloned()));
|
.set_ctx(ctx.as_ref().and_then(|c| c.sync_obj_ctx().cloned()));
|
||||||
|
|
||||||
|
|
@ -738,8 +743,21 @@ impl State {
|
||||||
let (output, ws) = match self.workspaces.get(name) {
|
let (output, ws) = match self.workspaces.get(name) {
|
||||||
Some(ws) => {
|
Some(ws) => {
|
||||||
let output = ws.output.get();
|
let output = ws.output.get();
|
||||||
|
let mut pinned_is_focused = false;
|
||||||
|
for pinned in output.pinned.iter() {
|
||||||
|
pinned
|
||||||
|
.deref()
|
||||||
|
.clone()
|
||||||
|
.node_visit(&mut generic_node_visitor(|node| {
|
||||||
|
node.node_seat_state().for_each_kb_focus(|s| {
|
||||||
|
pinned_is_focused |= s.id() == seat.id();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
let did_change = output.show_workspace(&ws);
|
let did_change = output.show_workspace(&ws);
|
||||||
ws.clone().node_do_focus(seat, Direction::Unspecified);
|
if !pinned_is_focused {
|
||||||
|
ws.clone().node_do_focus(seat, Direction::Unspecified);
|
||||||
|
}
|
||||||
if !did_change {
|
if !did_change {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1044,6 +1062,7 @@ impl State {
|
||||||
let (width, height) = target.logical_size(target_transform);
|
let (width, height) = target.logical_size(target_transform);
|
||||||
Rect::new_sized(0, 0, width, height).unwrap()
|
Rect::new_sized(0, 0, width, height).unwrap()
|
||||||
},
|
},
|
||||||
|
icons: None,
|
||||||
};
|
};
|
||||||
let mut sample_rect = SampleRect::identity();
|
let mut sample_rect = SampleRect::identity();
|
||||||
sample_rect.buffer_transform = transform;
|
sample_rect.buffer_transform = transform;
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,7 @@ impl ConnectorHandler {
|
||||||
tray_start_rel: Default::default(),
|
tray_start_rel: Default::default(),
|
||||||
tray_items: Default::default(),
|
tray_items: Default::default(),
|
||||||
ext_workspace_groups: Default::default(),
|
ext_workspace_groups: Default::default(),
|
||||||
|
pinned: Default::default(),
|
||||||
});
|
});
|
||||||
on.update_visible();
|
on.update_visible();
|
||||||
on.update_rects();
|
on.update_rects();
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,6 @@ impl Color {
|
||||||
Self::new(TransferFunction::Srgb, to_f32(r), to_f32(g), to_f32(b))
|
Self::new(TransferFunction::Srgb, to_f32(r), to_f32(g), to_f32(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
|
||||||
pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
Self::new_premultiplied(
|
Self::new_premultiplied(
|
||||||
TransferFunction::Srgb,
|
TransferFunction::Srgb,
|
||||||
|
|
@ -220,7 +219,6 @@ impl Color {
|
||||||
c
|
c
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
|
||||||
pub fn to_srgba_premultiplied(self) -> [u8; 4] {
|
pub fn to_srgba_premultiplied(self) -> [u8; 4] {
|
||||||
let [r, g, b, a] = self.to_array(TransferFunction::Srgb);
|
let [r, g, b, a] = self.to_array(TransferFunction::Srgb);
|
||||||
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
|
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
|
||||||
|
|
@ -332,7 +330,6 @@ impl Color {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
|
||||||
pub fn and_then(self, other: &Color) -> Color {
|
pub fn and_then(self, other: &Color) -> Color {
|
||||||
Color {
|
Color {
|
||||||
r: self.r * (1.0 - other.a) + other.r,
|
r: self.r * (1.0 - other.a) + other.r,
|
||||||
|
|
|
||||||
|
|
@ -2041,6 +2041,14 @@ impl ContainingNode for ContainerNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cnode_pinned(&self) -> bool {
|
||||||
|
self.tl_pinned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cnode_set_pinned(self: Rc<Self>, pinned: bool) {
|
||||||
|
self.tl_set_pinned(false, pinned);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelNodeBase for ContainerNode {
|
impl ToplevelNodeBase for ContainerNode {
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,10 @@ pub trait ContainingNode: Node {
|
||||||
let _ = new_y1;
|
let _ = new_y1;
|
||||||
let _ = new_y2;
|
let _ = new_y2;
|
||||||
}
|
}
|
||||||
|
fn cnode_pinned(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn cnode_set_pinned(self: Rc<Self>, pinned: bool) {
|
||||||
|
let _ = pinned;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use {
|
||||||
cursor_user::CursorUser,
|
cursor_user::CursorUser,
|
||||||
fixed::Fixed,
|
fixed::Fixed,
|
||||||
ifs::wl_seat::{
|
ifs::wl_seat::{
|
||||||
BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal,
|
BTN_LEFT, BTN_RIGHT, NodeSeatState, SeatId, WlSeatGlobal,
|
||||||
tablet::{TabletTool, TabletToolChanges, TabletToolId},
|
tablet::{TabletTool, TabletToolChanges, TabletToolId},
|
||||||
},
|
},
|
||||||
rect::Rect,
|
rect::Rect,
|
||||||
|
|
@ -15,7 +15,8 @@ use {
|
||||||
text::TextTexture,
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
||||||
StackedNode, TileDragDestination, ToplevelNode, WorkspaceNode, walker::NodeVisitor,
|
OutputNode, PinnedNode, StackedNode, TileDragDestination, ToplevelNode, WorkspaceNode,
|
||||||
|
walker::NodeVisitor,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState,
|
asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState,
|
||||||
|
|
@ -24,6 +25,7 @@ use {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ahash::AHashMap,
|
ahash::AHashMap,
|
||||||
|
arrayvec::ArrayVec,
|
||||||
std::{
|
std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
fmt::{Debug, Formatter},
|
fmt::{Debug, Formatter},
|
||||||
|
|
@ -41,6 +43,7 @@ pub struct FloatNode {
|
||||||
pub position: Cell<Rect>,
|
pub position: Cell<Rect>,
|
||||||
pub display_link: RefCell<Option<LinkedNode<Rc<dyn StackedNode>>>>,
|
pub display_link: RefCell<Option<LinkedNode<Rc<dyn StackedNode>>>>,
|
||||||
pub workspace_link: Cell<Option<LinkedNode<Rc<dyn StackedNode>>>>,
|
pub workspace_link: Cell<Option<LinkedNode<Rc<dyn StackedNode>>>>,
|
||||||
|
pub pinned_link: RefCell<Option<LinkedNode<Rc<dyn PinnedNode>>>>,
|
||||||
pub workspace: CloneCell<Rc<WorkspaceNode>>,
|
pub workspace: CloneCell<Rc<WorkspaceNode>>,
|
||||||
pub child: CloneCell<Option<Rc<dyn ToplevelNode>>>,
|
pub child: CloneCell<Option<Rc<dyn ToplevelNode>>>,
|
||||||
pub active: Cell<bool>,
|
pub active: Cell<bool>,
|
||||||
|
|
@ -119,6 +122,7 @@ impl FloatNode {
|
||||||
position: Cell::new(position),
|
position: Cell::new(position),
|
||||||
display_link: RefCell::new(None),
|
display_link: RefCell::new(None),
|
||||||
workspace_link: Cell::new(None),
|
workspace_link: Cell::new(None),
|
||||||
|
pinned_link: RefCell::new(None),
|
||||||
workspace: CloneCell::new(ws.clone()),
|
workspace: CloneCell::new(ws.clone()),
|
||||||
child: CloneCell::new(Some(child.clone())),
|
child: CloneCell::new(Some(child.clone())),
|
||||||
active: Cell::new(false),
|
active: Cell::new(false),
|
||||||
|
|
@ -143,6 +147,9 @@ impl FloatNode {
|
||||||
if floater.visible.get() {
|
if floater.visible.get() {
|
||||||
state.damage(position);
|
state.damage(position);
|
||||||
}
|
}
|
||||||
|
if child.tl_data().pinned.get() {
|
||||||
|
floater.toggle_pinned();
|
||||||
|
}
|
||||||
floater
|
floater
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,6 +223,9 @@ impl FloatNode {
|
||||||
let mut th = tr.height();
|
let mut th = tr.height();
|
||||||
let mut scalef = None;
|
let mut scalef = None;
|
||||||
let mut width = tr.width();
|
let mut width = tr.width();
|
||||||
|
if self.state.show_pin_icon.get() || self.pinned_link.borrow().is_some() {
|
||||||
|
width = (width - th).max(0);
|
||||||
|
}
|
||||||
if *scale != 1 {
|
if *scale != 1 {
|
||||||
let scale = scale.to_f64();
|
let scale = scale.to_f64();
|
||||||
th = (th as f64 * scale).round() as _;
|
th = (th as f64 * scale).round() as _;
|
||||||
|
|
@ -401,14 +411,72 @@ impl FloatNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_workspace(self: &Rc<Self>, ws: &Rc<WorkspaceNode>) {
|
fn set_workspace_(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
ws: &Rc<WorkspaceNode>,
|
||||||
|
update_pinned: bool,
|
||||||
|
update_visible: bool,
|
||||||
|
) {
|
||||||
if let Some(c) = self.child.get() {
|
if let Some(c) = self.child.get() {
|
||||||
c.tl_set_workspace(ws);
|
c.tl_set_workspace(ws);
|
||||||
}
|
}
|
||||||
self.workspace_link
|
self.workspace_link
|
||||||
.set(Some(ws.stacked.add_last(self.clone())));
|
.set(Some(ws.stacked.add_last(self.clone())));
|
||||||
self.workspace.set(ws.clone());
|
self.workspace.set(ws.clone());
|
||||||
self.stacked_set_visible(ws.float_visible());
|
if update_visible {
|
||||||
|
self.stacked_set_visible(ws.float_visible());
|
||||||
|
}
|
||||||
|
if update_pinned {
|
||||||
|
if let Some(pl) = &*self.pinned_link.borrow_mut() {
|
||||||
|
ws.output.get().pinned.add_last_existing(pl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn after_ws_move(self: &Rc<Self>, output: &Rc<OutputNode>) {
|
||||||
|
if let Some(pinned) = &*self.pinned_link.borrow() {
|
||||||
|
output.pinned.add_last_existing(pinned);
|
||||||
|
}
|
||||||
|
if output.is_dummy {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let pos = self.position.get();
|
||||||
|
let opos = output.global.pos.get();
|
||||||
|
if pos.intersects(&opos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let bw = self.state.theme.sizes.border_width.get();
|
||||||
|
let th = self.state.theme.sizes.title_height.get();
|
||||||
|
let mut x1 = pos.x1();
|
||||||
|
let mut x2 = pos.x2();
|
||||||
|
let mut y1 = pos.y1();
|
||||||
|
let mut y2 = pos.y2();
|
||||||
|
const DELTA: i32 = 100;
|
||||||
|
let delta = bw + DELTA;
|
||||||
|
macro_rules! adjust {
|
||||||
|
($z1:ident, $z2:ident) => {
|
||||||
|
if $z1 > opos.$z2() - delta {
|
||||||
|
$z1 = (opos.$z2() - delta).max(opos.$z1());
|
||||||
|
$z2 += $z1 - pos.$z1();
|
||||||
|
} else if $z2 < opos.$z1() + delta {
|
||||||
|
$z2 = (opos.$z1() + delta).min(opos.$z2());
|
||||||
|
$z1 += $z2 - pos.$z2();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
adjust!(x1, x2);
|
||||||
|
adjust!(y1, y2);
|
||||||
|
if y1 + bw + th <= opos.y1() {
|
||||||
|
y1 = opos.y1();
|
||||||
|
y2 += y1 - pos.y1();
|
||||||
|
}
|
||||||
|
let new_pos = Rect::new(x1, y1, x2, y2).unwrap();
|
||||||
|
self.position.set(new_pos);
|
||||||
|
if self.visible.get() {
|
||||||
|
self.state.damage(pos);
|
||||||
|
self.state.damage(new_pos);
|
||||||
|
}
|
||||||
|
self.schedule_layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_child_title(self: &Rc<Self>, title: &str) {
|
fn update_child_title(self: &Rc<Self>, title: &str) {
|
||||||
|
|
@ -461,6 +529,20 @@ impl FloatNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_pinned(self: &Rc<Self>) {
|
||||||
|
let pl = &mut *self.pinned_link.borrow_mut();
|
||||||
|
*pl = if pl.is_some() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let output = self.workspace.get().output.get();
|
||||||
|
Some(output.pinned.add_last(self.clone()))
|
||||||
|
};
|
||||||
|
if let Some(tl) = self.child.get() {
|
||||||
|
tl.tl_data().pinned.set(pl.is_some());
|
||||||
|
}
|
||||||
|
self.schedule_render_titles();
|
||||||
|
}
|
||||||
|
|
||||||
fn button(
|
fn button(
|
||||||
self: Rc<Self>,
|
self: Rc<Self>,
|
||||||
id: CursorType,
|
id: CursorType,
|
||||||
|
|
@ -474,6 +556,34 @@ impl FloatNode {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
let bw = self.state.theme.sizes.border_width.get();
|
||||||
|
let th = self.state.theme.sizes.title_height.get();
|
||||||
|
let mut is_icon_press = false;
|
||||||
|
if pressed && cursor_data.x >= bw && cursor_data.y >= bw && cursor_data.y < bw + th {
|
||||||
|
enum FloatIcon {
|
||||||
|
Pin,
|
||||||
|
}
|
||||||
|
let mut icons = ArrayVec::<FloatIcon, 1>::new();
|
||||||
|
if self.state.show_pin_icon.get() || self.pinned_link.borrow().is_some() {
|
||||||
|
icons.push(FloatIcon::Pin);
|
||||||
|
}
|
||||||
|
let mut x2 = bw + th;
|
||||||
|
let icon = 'icon: {
|
||||||
|
for icon in icons {
|
||||||
|
if cursor_data.x < x2 {
|
||||||
|
break 'icon Some(icon);
|
||||||
|
}
|
||||||
|
x2 += th;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(icon) = icon {
|
||||||
|
is_icon_press = true;
|
||||||
|
match icon {
|
||||||
|
FloatIcon::Pin => self.toggle_pinned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if !cursor_data.op_active {
|
if !cursor_data.op_active {
|
||||||
if !pressed {
|
if !pressed {
|
||||||
return;
|
return;
|
||||||
|
|
@ -489,6 +599,7 @@ impl FloatNode {
|
||||||
cursor_data.x,
|
cursor_data.x,
|
||||||
cursor_data.y,
|
cursor_data.y,
|
||||||
) && cursor_data.op_type == OpType::Move
|
) && cursor_data.op_type == OpType::Move
|
||||||
|
&& !is_icon_press
|
||||||
{
|
{
|
||||||
if let Some(tl) = self.child.get() {
|
if let Some(tl) = self.child.get() {
|
||||||
drop(cursors);
|
drop(cursors);
|
||||||
|
|
@ -528,7 +639,7 @@ impl FloatNode {
|
||||||
} else if !pressed {
|
} else if !pressed {
|
||||||
cursor_data.op_active = false;
|
cursor_data.op_active = false;
|
||||||
let ws = cursor.output().ensure_workspace();
|
let ws = cursor.output().ensure_workspace();
|
||||||
self.set_workspace(&ws);
|
self.set_workspace_(&ws, true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -637,6 +748,9 @@ impl Node for FloatNode {
|
||||||
state: KeyState,
|
state: KeyState,
|
||||||
_serial: u64,
|
_serial: u64,
|
||||||
) {
|
) {
|
||||||
|
if button == BTN_RIGHT && state == KeyState::Pressed {
|
||||||
|
self.toggle_pinned();
|
||||||
|
}
|
||||||
if button != BTN_LEFT {
|
if button != BTN_LEFT {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -756,6 +870,7 @@ impl ContainingNode for FloatNode {
|
||||||
self.child.set(None);
|
self.child.set(None);
|
||||||
self.display_link.borrow_mut().take();
|
self.display_link.borrow_mut().take();
|
||||||
self.workspace_link.set(None);
|
self.workspace_link.set(None);
|
||||||
|
self.pinned_link.take();
|
||||||
if self.visible.get() {
|
if self.visible.get() {
|
||||||
self.state.damage(self.position.get());
|
self.state.damage(self.position.get());
|
||||||
}
|
}
|
||||||
|
|
@ -830,6 +945,17 @@ impl ContainingNode for FloatNode {
|
||||||
self.schedule_layout();
|
self.schedule_layout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cnode_pinned(&self) -> bool {
|
||||||
|
self.pinned_link.borrow().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cnode_set_pinned(self: Rc<Self>, pinned: bool) {
|
||||||
|
if self.pinned_link.borrow().is_some() == pinned {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.toggle_pinned();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StackedNode for FloatNode {
|
impl StackedNode for FloatNode {
|
||||||
|
|
@ -847,3 +973,9 @@ impl StackedNode for FloatNode {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PinnedNode for FloatNode {
|
||||||
|
fn set_workspace(self: Rc<Self>, workspace: &Rc<WorkspaceNode>, update_visible: bool) {
|
||||||
|
self.set_workspace_(workspace, false, update_visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,9 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
text::TextTexture,
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, StackedNode,
|
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, PinnedNode,
|
||||||
TddType, TileDragDestination, WorkspaceDragDestination, WorkspaceNode, WorkspaceNodeId,
|
StackedNode, TddType, TileDragDestination, WorkspaceDragDestination, WorkspaceNode,
|
||||||
walker::NodeVisitor,
|
WorkspaceNodeId, walker::NodeVisitor,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
||||||
|
|
@ -103,6 +103,7 @@ pub struct OutputNode {
|
||||||
pub tray_start_rel: Cell<i32>,
|
pub tray_start_rel: Cell<i32>,
|
||||||
pub tray_items: LinkedList<Rc<dyn DynTrayItem>>,
|
pub tray_items: LinkedList<Rc<dyn DynTrayItem>>,
|
||||||
pub ext_workspace_groups: CopyHashMap<WorkspaceManagerId, Rc<ExtWorkspaceGroupHandleV1>>,
|
pub ext_workspace_groups: CopyHashMap<WorkspaceManagerId, Rc<ExtWorkspaceGroupHandleV1>>,
|
||||||
|
pub pinned: LinkedList<Rc<dyn PinnedNode>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
|
@ -646,6 +647,9 @@ impl OutputNode {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
collect_kb_foci2(old.clone(), &mut seats);
|
collect_kb_foci2(old.clone(), &mut seats);
|
||||||
|
for pinned in self.pinned.iter() {
|
||||||
|
pinned.deref().clone().set_workspace(ws, false);
|
||||||
|
}
|
||||||
if old.is_empty() {
|
if old.is_empty() {
|
||||||
for jw in old.jay_workspaces.lock().values() {
|
for jw in old.jay_workspaces.lock().values() {
|
||||||
jw.send_destroyed();
|
jw.send_destroyed();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::tree::Node;
|
use {
|
||||||
|
crate::tree::{Node, WorkspaceNode},
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
pub trait StackedNode: Node {
|
pub trait StackedNode: Node {
|
||||||
fn stacked_prepare_set_visible(&self) {
|
fn stacked_prepare_set_visible(&self) {
|
||||||
|
|
@ -14,3 +17,7 @@ pub trait StackedNode: Node {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait PinnedNode: StackedNode {
|
||||||
|
fn set_workspace(self: Rc<Self>, workspace: &Rc<WorkspaceNode>, update_visible: bool);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ pub trait ToplevelNode: ToplevelNodeBase {
|
||||||
fn tl_change_extents(self: Rc<Self>, rect: &Rect);
|
fn tl_change_extents(self: Rc<Self>, rect: &Rect);
|
||||||
fn tl_set_visible(&self, visible: bool);
|
fn tl_set_visible(&self, visible: bool);
|
||||||
fn tl_destroy(&self);
|
fn tl_destroy(&self);
|
||||||
|
fn tl_pinned(&self) -> bool;
|
||||||
|
fn tl_set_pinned(&self, self_pinned: bool, pinned: bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ToplevelNodeBase> ToplevelNode for T {
|
impl<T: ToplevelNodeBase> ToplevelNode for T {
|
||||||
|
|
@ -151,6 +153,24 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
||||||
self.tl_data().destroy_node(self);
|
self.tl_data().destroy_node(self);
|
||||||
self.tl_destroy_impl();
|
self.tl_destroy_impl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tl_pinned(&self) -> bool {
|
||||||
|
let Some(parent) = self.tl_data().parent.get() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
parent.cnode_pinned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tl_set_pinned(&self, self_pinned: bool, pinned: bool) {
|
||||||
|
let data = self.tl_data();
|
||||||
|
if self_pinned {
|
||||||
|
data.pinned.set(pinned);
|
||||||
|
}
|
||||||
|
let Some(parent) = data.parent.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
parent.cnode_set_pinned(pinned);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ToplevelNodeBase: Node {
|
pub trait ToplevelNodeBase: Node {
|
||||||
|
|
@ -243,6 +263,7 @@ pub struct ToplevelData {
|
||||||
pub is_floating: Cell<bool>,
|
pub is_floating: Cell<bool>,
|
||||||
pub float_width: Cell<i32>,
|
pub float_width: Cell<i32>,
|
||||||
pub float_height: Cell<i32>,
|
pub float_height: Cell<i32>,
|
||||||
|
pub pinned: Cell<bool>,
|
||||||
pub is_fullscreen: Cell<bool>,
|
pub is_fullscreen: Cell<bool>,
|
||||||
pub fullscrceen_data: RefCell<Option<FullscreenedData>>,
|
pub fullscrceen_data: RefCell<Option<FullscreenedData>>,
|
||||||
pub workspace: CloneCell<Option<Rc<WorkspaceNode>>>,
|
pub workspace: CloneCell<Option<Rc<WorkspaceNode>>>,
|
||||||
|
|
@ -283,6 +304,7 @@ impl ToplevelData {
|
||||||
is_floating: Default::default(),
|
is_floating: Default::default(),
|
||||||
float_width: Default::default(),
|
float_width: Default::default(),
|
||||||
float_height: Default::default(),
|
float_height: Default::default(),
|
||||||
|
pinned: Cell::new(false),
|
||||||
is_fullscreen: Default::default(),
|
is_fullscreen: Default::default(),
|
||||||
fullscrceen_data: Default::default(),
|
fullscrceen_data: Default::default(),
|
||||||
workspace: Default::default(),
|
workspace: Default::default(),
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
text::TextTexture,
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FloatNode, FoundNode, Node,
|
||||||
NodeVisitorBase, OutputNode, PlaceholderNode, StackedNode, ToplevelNode,
|
NodeId, NodeVisitorBase, OutputNode, PlaceholderNode, StackedNode, ToplevelNode,
|
||||||
container::ContainerNode, walker::NodeVisitor,
|
container::ContainerNode, walker::NodeVisitor,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
|
|
@ -127,6 +127,11 @@ impl WorkspaceNode {
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_float(&mut self, node: &Rc<FloatNode>) {
|
||||||
|
node.after_ws_move(self.0);
|
||||||
|
node.node_visit_children(self);
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_xwindow(&mut self, node: &Rc<Xwindow>) {
|
fn visit_xwindow(&mut self, node: &Rc<Xwindow>) {
|
||||||
node.tl_workspace_output_changed();
|
node.tl_workspace_output_changed();
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
|
|
@ -421,6 +426,27 @@ pub fn move_ws_to_output(
|
||||||
config: WsMoveConfig,
|
config: WsMoveConfig,
|
||||||
) {
|
) {
|
||||||
let source = ws.output.get();
|
let source = ws.output.get();
|
||||||
|
if let Some(visible) = source.workspace.get() {
|
||||||
|
if visible.id == ws.id {
|
||||||
|
source.workspace.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut new_source_ws = None;
|
||||||
|
if !config.source_is_destroyed && !source.is_dummy && source.workspace.is_none() {
|
||||||
|
new_source_ws = source
|
||||||
|
.workspaces
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.id != ws.id)
|
||||||
|
.map(|c| (*c).clone());
|
||||||
|
if new_source_ws.is_none() && source.pinned.is_not_empty() {
|
||||||
|
new_source_ws = Some(source.generate_workspace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(new_source_ws) = &new_source_ws {
|
||||||
|
for pinned in source.pinned.iter() {
|
||||||
|
pinned.deref().clone().set_workspace(new_source_ws, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
ws.set_output(&target);
|
ws.set_output(&target);
|
||||||
'link: {
|
'link: {
|
||||||
if let Some(before) = config.before {
|
if let Some(before) = config.before {
|
||||||
|
|
@ -440,18 +466,9 @@ pub fn move_ws_to_output(
|
||||||
ws.set_visible(false);
|
ws.set_visible(false);
|
||||||
}
|
}
|
||||||
ws.flush_jay_workspaces();
|
ws.flush_jay_workspaces();
|
||||||
if let Some(visible) = source.workspace.get() {
|
if let Some(ws) = new_source_ws {
|
||||||
if visible.id == ws.id {
|
source.show_workspace(&ws);
|
||||||
source.workspace.take();
|
ws.flush_jay_workspaces();
|
||||||
}
|
|
||||||
}
|
|
||||||
if !config.source_is_destroyed && !source.is_dummy {
|
|
||||||
if source.workspace.is_none() {
|
|
||||||
if let Some(ws) = source.workspaces.first() {
|
|
||||||
source.show_workspace(&ws);
|
|
||||||
ws.flush_jay_workspaces();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !target.is_dummy {
|
if !target.is_dummy {
|
||||||
target.schedule_update_render_data();
|
target.schedule_update_render_data();
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ use {
|
||||||
parsers::{
|
parsers::{
|
||||||
color_management::ColorManagement,
|
color_management::ColorManagement,
|
||||||
config::{ConfigParser, ConfigParserError},
|
config::{ConfigParser, ConfigParserError},
|
||||||
|
float::Float,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
toml::{self},
|
toml::{self},
|
||||||
|
|
@ -58,6 +59,8 @@ pub enum SimpleCommand {
|
||||||
EnableWindowManagement(bool),
|
EnableWindowManagement(bool),
|
||||||
SetFloatAboveFullscreen(bool),
|
SetFloatAboveFullscreen(bool),
|
||||||
ToggleFloatAboveFullscreen,
|
ToggleFloatAboveFullscreen,
|
||||||
|
SetFloatPinned(bool),
|
||||||
|
ToggleFloatPinned,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -367,6 +370,7 @@ pub struct Config {
|
||||||
pub ui_drag: UiDrag,
|
pub ui_drag: UiDrag,
|
||||||
pub xwayland: Option<Xwayland>,
|
pub xwayland: Option<Xwayland>,
|
||||||
pub color_management: Option<ColorManagement>,
|
pub color_management: Option<ColorManagement>,
|
||||||
|
pub float: Option<Float>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ mod drm_device;
|
||||||
mod drm_device_match;
|
mod drm_device_match;
|
||||||
mod env;
|
mod env;
|
||||||
pub mod exec;
|
pub mod exec;
|
||||||
|
pub mod float;
|
||||||
mod format;
|
mod format;
|
||||||
mod gfx_api;
|
mod gfx_api;
|
||||||
mod idle;
|
mod idle;
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,9 @@ impl ActionParser<'_> {
|
||||||
"enable-float-above-fullscreen" => SetFloatAboveFullscreen(true),
|
"enable-float-above-fullscreen" => SetFloatAboveFullscreen(true),
|
||||||
"disable-float-above-fullscreen" => SetFloatAboveFullscreen(false),
|
"disable-float-above-fullscreen" => SetFloatAboveFullscreen(false),
|
||||||
"toggle-float-above-fullscreen" => ToggleFloatAboveFullscreen,
|
"toggle-float-above-fullscreen" => ToggleFloatAboveFullscreen,
|
||||||
|
"pin-float" => SetFloatPinned(true),
|
||||||
|
"unpin-float" => SetFloatPinned(false),
|
||||||
|
"toggle-float-pinned" => ToggleFloatPinned,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(
|
return Err(
|
||||||
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)
|
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use {
|
||||||
drm_device::DrmDevicesParser,
|
drm_device::DrmDevicesParser,
|
||||||
drm_device_match::DrmDeviceMatchParser,
|
drm_device_match::DrmDeviceMatchParser,
|
||||||
env::EnvParser,
|
env::EnvParser,
|
||||||
|
float::FloatParser,
|
||||||
gfx_api::GfxApiParser,
|
gfx_api::GfxApiParser,
|
||||||
idle::IdleParser,
|
idle::IdleParser,
|
||||||
input::InputsParser,
|
input::InputsParser,
|
||||||
|
|
@ -118,7 +119,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
ui_drag_val,
|
ui_drag_val,
|
||||||
xwayland_val,
|
xwayland_val,
|
||||||
),
|
),
|
||||||
(color_management_val,),
|
(color_management_val, float_val),
|
||||||
) = ext.extract((
|
) = ext.extract((
|
||||||
(
|
(
|
||||||
opt(val("keymap")),
|
opt(val("keymap")),
|
||||||
|
|
@ -156,7 +157,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
opt(val("ui-drag")),
|
opt(val("ui-drag")),
|
||||||
opt(val("xwayland")),
|
opt(val("xwayland")),
|
||||||
),
|
),
|
||||||
(opt(val("color-management")),),
|
(opt(val("color-management")), opt(val("float"))),
|
||||||
))?;
|
))?;
|
||||||
let mut keymap = None;
|
let mut keymap = None;
|
||||||
if let Some(value) = keymap_val {
|
if let Some(value) = keymap_val {
|
||||||
|
|
@ -381,6 +382,15 @@ impl Parser for ConfigParser<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut float = None;
|
||||||
|
if let Some(value) = float_val {
|
||||||
|
match value.parse(&mut FloatParser(self.0)) {
|
||||||
|
Ok(v) => float = Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse the float settings: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(Config {
|
Ok(Config {
|
||||||
keymap,
|
keymap,
|
||||||
repeat_rate,
|
repeat_rate,
|
||||||
|
|
@ -412,6 +422,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
ui_drag,
|
ui_drag,
|
||||||
xwayland,
|
xwayland,
|
||||||
color_management,
|
color_management,
|
||||||
|
float,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
48
toml-config/src/config/parsers/float.rs
Normal file
48
toml-config/src/config/parsers/float.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{Extractor, ExtractorError, bol, opt, recover},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum FloatParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FloatParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Float {
|
||||||
|
pub show_pin_icon: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser for FloatParser<'_> {
|
||||||
|
type Value = Float;
|
||||||
|
type Error = FloatParserError;
|
||||||
|
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 (show_pin_icon,) = ext.extract((recover(opt(bol("show-pin-icon"))),))?;
|
||||||
|
Ok(Float {
|
||||||
|
show_pin_icon: show_pin_icon.despan(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,8 @@ use {
|
||||||
logging::set_log_level,
|
logging::set_log_level,
|
||||||
on_devices_enumerated, on_idle, quit, reload, set_color_management_enabled,
|
on_devices_enumerated, on_idle, quit, reload, set_color_management_enabled,
|
||||||
set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen,
|
set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen,
|
||||||
set_idle, set_idle_grace_period, set_ui_drag_enabled, set_ui_drag_threshold,
|
set_idle, set_idle_grace_period, set_show_float_pin_icon, set_ui_drag_enabled,
|
||||||
|
set_ui_drag_threshold,
|
||||||
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
||||||
switch_to_vt,
|
switch_to_vt,
|
||||||
theme::{reset_colors, reset_font, reset_sizes, set_font},
|
theme::{reset_colors, reset_font, reset_sizes, set_font},
|
||||||
|
|
@ -101,6 +102,8 @@ impl Action {
|
||||||
B::new(move || set_float_above_fullscreen(bool))
|
B::new(move || set_float_above_fullscreen(bool))
|
||||||
}
|
}
|
||||||
SimpleCommand::ToggleFloatAboveFullscreen => B::new(toggle_float_above_fullscreen),
|
SimpleCommand::ToggleFloatAboveFullscreen => B::new(toggle_float_above_fullscreen),
|
||||||
|
SimpleCommand::SetFloatPinned(pinned) => B::new(move || s.set_float_pinned(pinned)),
|
||||||
|
SimpleCommand::ToggleFloatPinned => B::new(move || s.toggle_float_pinned()),
|
||||||
},
|
},
|
||||||
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();
|
||||||
|
|
@ -1096,6 +1099,11 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
||||||
set_color_management_enabled(enabled);
|
set_color_management_enabled(enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(float) = config.float {
|
||||||
|
if let Some(show) = float.show_pin_icon {
|
||||||
|
set_show_float_pin_icon(show);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_command(exec: &Exec) -> Command {
|
fn create_command(exec: &Exec) -> Command {
|
||||||
|
|
|
||||||
|
|
@ -636,6 +636,10 @@
|
||||||
"color-management": {
|
"color-management": {
|
||||||
"description": "Configures the color-management settings.\n\n- Example:\n\n ```toml\n [color-management]\n enabled = true\n ```\n",
|
"description": "Configures the color-management settings.\n\n- Example:\n\n ```toml\n [color-management]\n enabled = true\n ```\n",
|
||||||
"$ref": "#/$defs/ColorManagement"
|
"$ref": "#/$defs/ColorManagement"
|
||||||
|
},
|
||||||
|
"float": {
|
||||||
|
"description": "Configures the settings of floating windows.\n\n- Example:\n\n ```toml\n [float]\n show-pin-icon = true\n ```\n",
|
||||||
|
"$ref": "#/$defs/Float"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
|
@ -808,6 +812,17 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"Float": {
|
||||||
|
"description": "Describes settings of floating windows.\n\n- Example:\n\n ```toml\n [float]\n show-pin-icon = true\n ```\n",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"show-pin-icon": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Sets whether floating windows always show a pin icon.\n\nThe default is `false`.\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
},
|
||||||
"Format": {
|
"Format": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "A graphics format.\n\nThese formats are documented in https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_fourcc.h\n\n- Example:\n\n ```toml\n [[outputs]]\n match.serial-number = \"33K03894SL0\"\n format = \"rgb565\"\n ```\n",
|
"description": "A graphics format.\n\nThese formats are documented in https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_fourcc.h\n\n- Example:\n\n ```toml\n [[outputs]]\n match.serial-number = \"33K03894SL0\"\n format = \"rgb565\"\n ```\n",
|
||||||
|
|
@ -1284,7 +1299,10 @@
|
||||||
"disable-window-management",
|
"disable-window-management",
|
||||||
"enable-float-above-fullscreen",
|
"enable-float-above-fullscreen",
|
||||||
"disable-float-above-fullscreen",
|
"disable-float-above-fullscreen",
|
||||||
"toggle-float-above-fullscreen"
|
"toggle-float-above-fullscreen",
|
||||||
|
"pin-float",
|
||||||
|
"unpin-float",
|
||||||
|
"toggle-float-pinned"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Status": {
|
"Status": {
|
||||||
|
|
|
||||||
|
|
@ -1265,6 +1265,19 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a [ColorManagement](#types-ColorManagement).
|
The value of this field should be a [ColorManagement](#types-ColorManagement).
|
||||||
|
|
||||||
|
- `float` (optional):
|
||||||
|
|
||||||
|
Configures the settings of floating windows.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[float]
|
||||||
|
show-pin-icon = true
|
||||||
|
```
|
||||||
|
|
||||||
|
The value of this field should be a [Float](#types-Float).
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Connector"></a>
|
<a name="types-Connector"></a>
|
||||||
### `Connector`
|
### `Connector`
|
||||||
|
|
@ -1629,6 +1642,31 @@ The table has the following fields:
|
||||||
The value of this field should be a boolean.
|
The value of this field should be a boolean.
|
||||||
|
|
||||||
|
|
||||||
|
<a name="types-Float"></a>
|
||||||
|
### `Float`
|
||||||
|
|
||||||
|
Describes settings of floating windows.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[float]
|
||||||
|
show-pin-icon = true
|
||||||
|
```
|
||||||
|
|
||||||
|
Values of this type should be tables.
|
||||||
|
|
||||||
|
The table has the following fields:
|
||||||
|
|
||||||
|
- `show-pin-icon` (optional):
|
||||||
|
|
||||||
|
Sets whether floating windows always show a pin icon.
|
||||||
|
|
||||||
|
The default is `false`.
|
||||||
|
|
||||||
|
The value of this field should be a boolean.
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Format"></a>
|
<a name="types-Format"></a>
|
||||||
### `Format`
|
### `Format`
|
||||||
|
|
||||||
|
|
@ -2905,6 +2943,21 @@ The string should have one of the following values:
|
||||||
|
|
||||||
Toggles floating windows showing above fullscreen windows.
|
Toggles floating windows showing above fullscreen windows.
|
||||||
|
|
||||||
|
- `pin-float`:
|
||||||
|
|
||||||
|
Pins the currently focused floating window.
|
||||||
|
|
||||||
|
If a floating window is pinned, it will stay visible even when switching to a
|
||||||
|
different workspace.
|
||||||
|
|
||||||
|
- `unpin-float`:
|
||||||
|
|
||||||
|
Unpins the currently focused floating window.
|
||||||
|
|
||||||
|
- `toggle-float-pinned`:
|
||||||
|
|
||||||
|
Toggles whether the currently focused floating window is pinned.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Status"></a>
|
<a name="types-Status"></a>
|
||||||
|
|
|
||||||
|
|
@ -712,6 +712,18 @@ SimpleActionName:
|
||||||
- value: toggle-float-above-fullscreen
|
- value: toggle-float-above-fullscreen
|
||||||
description: |
|
description: |
|
||||||
Toggles floating windows showing above fullscreen windows.
|
Toggles floating windows showing above fullscreen windows.
|
||||||
|
- value: pin-float
|
||||||
|
description: |
|
||||||
|
Pins the currently focused floating window.
|
||||||
|
|
||||||
|
If a floating window is pinned, it will stay visible even when switching to a
|
||||||
|
different workspace.
|
||||||
|
- value: unpin-float
|
||||||
|
description: |
|
||||||
|
Unpins the currently focused floating window.
|
||||||
|
- value: toggle-float-pinned
|
||||||
|
description: |
|
||||||
|
Toggles whether the currently focused floating window is pinned.
|
||||||
|
|
||||||
|
|
||||||
Color:
|
Color:
|
||||||
|
|
@ -2326,6 +2338,18 @@ Config:
|
||||||
[color-management]
|
[color-management]
|
||||||
enabled = true
|
enabled = true
|
||||||
```
|
```
|
||||||
|
float:
|
||||||
|
ref: Float
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
Configures the settings of floating windows.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[float]
|
||||||
|
show-pin-icon = true
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Idle:
|
Idle:
|
||||||
|
|
@ -2834,3 +2858,24 @@ Brightness:
|
||||||
- kind: number
|
- kind: number
|
||||||
description: |
|
description: |
|
||||||
The brightness in cd/m^2.
|
The brightness in cd/m^2.
|
||||||
|
|
||||||
|
|
||||||
|
Float:
|
||||||
|
kind: table
|
||||||
|
description: |
|
||||||
|
Describes settings of floating windows.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[float]
|
||||||
|
show-pin-icon = true
|
||||||
|
```
|
||||||
|
fields:
|
||||||
|
show-pin-icon:
|
||||||
|
description: |
|
||||||
|
Sets whether floating windows always show a pin icon.
|
||||||
|
|
||||||
|
The default is `false`.
|
||||||
|
kind: boolean
|
||||||
|
required: false
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue