use crate::backend::ButtonState;
use crate::cursor::KnownCursor;
use crate::cursor_user::CursorUser;
use crate::fixed::Fixed;
use crate::icons::titles::TitleIconKey;
use crate::icons::titles::TitleIconsUser;
use crate::ifs::wl_seat::BTN_LEFT;
use crate::ifs::wl_seat::BTN_RIGHT;
use crate::ifs::wl_seat::NodeSeatState;
use crate::ifs::wl_seat::SeatId;
use crate::ifs::wl_seat::WlSeatGlobal;
use crate::ifs::wl_seat::tablet::TabletTool;
use crate::ifs::wl_seat::tablet::TabletToolChanges;
use crate::ifs::wl_seat::tablet::TabletToolId;
use crate::ifs::wl_surface::icon_surface::IconSurfaceOwner;
use crate::ifs::wl_surface::icon_surface::jay_icon_surface_v1::IconSurface;
use crate::rect::Rect;
use crate::renderer::Renderer;
use crate::scale::Scale;
use crate::state::GfxCtxChangedListener;
use crate::state::ScalesChangedListener;
use crate::state::State;
use crate::state::ThemeChangeListener;
use crate::text::TextTexture;
use crate::theme::Color;
use crate::theme::compute_focused_border;
use crate::theme::compute_title_height;
use crate::theme::title_icon_size;
use crate::theme::title_plus_underline_height;
use crate::theme::title_underline_height;
use crate::transactions::TransactionData;
use crate::transactions::Transactionable;
use crate::transactions::TransactionableExt;
use crate::tree::ContainingNode;
use crate::tree::Direction;
use crate::tree::FindTreeResult;
use crate::tree::FindTreeUsecase;
use crate::tree::FoundNode;
use crate::tree::Node;
use crate::tree::NodeBase;
use crate::tree::NodeId;
use crate::tree::NodeLayerLink;
use crate::tree::NodeLocation;
use crate::tree::NodeStackTransactionOp;
use crate::tree::NodesStack;
use crate::tree::NodesStackElement;
use crate::tree::OutputNode;
use crate::tree::PinnedNode;
use crate::tree::SplitView;
use crate::tree::StackedNode;
use crate::tree::TileDragDestination;
use crate::tree::ToplevelData;
use crate::tree::ToplevelNode;
use crate::tree::ToplevelThemeType::ParentTheme;
use crate::tree::TreeTimeline;
use crate::tree::TreeTimeline::LiveTL;
use crate::tree::TreeTimeline::RenderTL;
use crate::tree::WorkspaceChangeReason;
use crate::tree::WorkspaceEventListener;
use crate::tree::WorkspaceNode;
use crate::tree::WorkspaceType;
use crate::tree::toplevel_set_floating;
use crate::tree::walker::NodeVisitor;
use crate::utils::asyncevent::AsyncEvent;
use crate::utils::bhash::BHashMap;
use crate::utils::bool_ext::BoolExt;
use crate::utils::cached_value::CachedValue;
use crate::utils::clamp_ext::ClampExt;
use crate::utils::clonecell::CloneCell;
use crate::utils::double_click_state::DoubleClickState;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::event_listener::EventListener;
use crate::utils::fuse::fuse_inode::FuseInodeWithKey;
use crate::utils::linkedlist::LinkedNode;
use crate::utils::liveness::Liveness;
use crate::utils::on_drop_event::OnDropEvent;
use crate::utils::smallmap::SmallMapMut;
use arrayvec::ArrayVec;
use derivative::Derivative;
use jay_proc::CachedValue;
use jay_proc::GetLiveness;
use std::cell::Cell;
use std::cell::RefCell;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::mem;
use std::ops::Deref;
use std::rc::Rc;
use std::sync::Arc;
mod float_dfs_g_fuse;
tree_id!(FloatNodeId);
#[derive(GetLiveness)]
pub struct FloatNode {
id: FloatNodeId,
state: Rc<State>,
pub node_state: SplitView<FloatNodeState>,
display_link: RefCell<NodesStackElement>,
workspace_link: Cell<Option<LinkedNode<Rc<dyn StackedNode>>>>,
pinned_link: RefCell<Option<LinkedNode<Rc<dyn PinnedNode>>>>,
workspace: CloneCell<Rc<WorkspaceNode>>,
location: Cell<NodeLocation>,
seat_state: NodeSeatState,
layout_phase_scheduled: Cell<bool>,
layout_scheduled: Cell<bool>,
title_offsets_scheduled: Cell<bool>,
render_titles_scheduled: Cell<bool>,
title: RefCell<String>,
pub title_textures: RefCell<SmallMapMut<Scale, TextTexture, 2>>,
pub needs_initial_size: Cell<bool>,
pub title_icons: TitleIconsUser,
cursors: RefCell<BHashMap<CursorType, CursorState>>,
transaction_data: TransactionData<FloatTransactionOp>,
workspace_listener: EventListener<dyn WorkspaceEventListener>,
_theme_listener: EventListener<dyn ThemeChangeListener>,
_gfx_ctx_listener: EventListener<dyn GfxCtxChangedListener>,
_scales_listener: EventListener<dyn ScalesChangedListener>,
liveness: Liveness,
}
#[derive(Derivative)]
#[derivative(Default)]
pub struct FloatNodeState {
visible: Cell<bool>,
requested_visible: Cell<bool>,
pub position: Cell<Rect>,
pub child: CloneCell<Option<Rc<dyn ToplevelNode>>>,
child_is_placeholder: Cell<bool>,
#[derivative(Default(value = "Cell::new(WorkspaceType::Normal)"))]
pub workspace_ty: Cell<WorkspaceType>,
pub title_rect: Cell<Rect>,
pub active: Cell<bool>,
pub attention_requested: Cell<bool>,
pub pinned: Cell<bool>,
pub theme: FloatTheme,
pub offsets: Offsets,
pub toplevel_icon: CloneCell<Option<IconSurface>>,
}
#[derive(CachedValue)]
pub struct Offsets {
pub overlay_icon: Cell<i32>,
pub pin_icon: Cell<i32>,
pub toplevel_icon: Cell<i32>,
pub title: Cell<i32>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
enum CursorType {
Seat(SeatId),
TabletTool(TabletToolId),
}
struct CursorState {
cursor: KnownCursor,
target: bool,
x: i32,
y: i32,
op_type: OpType,
op_active: bool,
dist_hor: i32,
dist_ver: i32,
double_click_state: DoubleClickState,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum OpType {
Move,
ResizeLeft,
ResizeTop,
ResizeRight,
ResizeBottom,
ResizeTopLeft,
ResizeTopRight,
ResizeBottomLeft,
ResizeBottomRight,
}
#[derive(Clone, CachedValue)]
pub struct FloatTheme {
pub colors: FloatThemeColors,
pub sizes: FloatThemeSizes,
pub show_pin_icon: Cell<bool>,
pub window_icons_grayscale: Cell<bool>,
pub title_font: CloneCell<Rc<Arc<str>>>,
}
#[derive(Clone, CachedValue)]
pub struct FloatThemeColors {
focused_title_text: Cell<Color>,
unfocused_title_text: Cell<Color>,
pub focused_border: Cell<Color>,
pub border: Cell<Color>,
pub focused_title_background: Cell<Color>,
pub attention_requested_background: Cell<Color>,
pub unfocused_title_background: Cell<Color>,
pub separator: Cell<Color>,
}
#[derive(Clone, CachedValue)]
pub struct FloatThemeSizes {
pub border_width: Cell<i32>,
pub title_height: Cell<i32>,
pub title_plus_underline_height: Cell<i32>,
pub title_underline_height: Cell<i32>,
pub title_icon_size: Cell<i32>,
}
pub async fn float_layout_phase(state: Rc<State>) {
loop {
let node = state.pending_float_layout_phase.pop().await;
node.layout_phase_scheduled.take();
if node.layout_scheduled.get() {
node.perform_layout();
}
if node.title_offsets_scheduled.get() {
node.update_title_offsets();
}
}
}
pub async fn float_titles(state: Rc<State>) {
loop {
let node = state.pending_float_titles.pop().await;
if node.render_titles_scheduled.get() {
node.render_titles_scheduled.set(false);
node.render_title_phase1().triggered().await;
node.render_title_phase2();
}
}
}
impl State {
fn float_stack(&self, ty: WorkspaceType) -> &Rc<NodesStack> {
match ty {
WorkspaceType::Normal => &self.root.stacked,
WorkspaceType::Overlay => &self.root.stacked_in_overlay,
}
}
}
impl FloatNode {
pub fn new(
state: &Rc<State>,
ws: &Rc<WorkspaceNode>,
abs_pos: Option<(i32, i32)>,
inner_width: i32,
inner_height: i32,
child: Rc<dyn ToplevelNode>,
) -> Rc<Self> {
let output = ws.node_state[LiveTL].output.get();
let floater = Rc::<FloatNode>::new_cyclic(|slf| FloatNode {
id: state.node_ids.next(),
state: state.clone(),
node_state: Default::default(),
display_link: state.float_stack(ws.ty).element(),
workspace_link: Cell::new(None),
pinned_link: RefCell::new(None),
workspace: CloneCell::new(ws.clone()),
location: Cell::new(ws.location()),
seat_state: Default::default(),
layout_phase_scheduled: Default::default(),
layout_scheduled: Cell::new(false),
title_offsets_scheduled: Default::default(),
render_titles_scheduled: Cell::new(false),
title: Default::default(),
title_textures: Default::default(),
needs_initial_size: Cell::new(output.is_dummy),
title_icons: state.icons.title_user(),
cursors: Default::default(),
transaction_data: TransactionData::new(&state.tree),
workspace_listener: EventListener::attached(slf.clone(), &ws.listeners),
_theme_listener: EventListener::attached(slf.clone(), &state.theme_listeners),
_gfx_ctx_listener: EventListener::attached(slf.clone(), &state.gfx_ctx_changed),
_scales_listener: EventListener::attached(slf.clone(), &state.scales_changed),
liveness: Default::default(),
});
let tl_data = child.tl_data();
{
let theme = floater.compute_theme(tl_data);
floater.node_state[LiveTL].theme.cached_set(theme.clone());
floater.node_state[RenderTL].theme.cached_set(theme);
floater.add_transaction_op(FloatTransactionOp::UpdateTitleIcons);
}
let ns = &floater.node_state[LiveTL];
let theme = &ns.theme;
let bw = theme.sizes.border_width.get();
let tpuh = theme.sizes.title_plus_underline_height.get();
let width = inner_width + 2 * bw;
let height = inner_height + 2 * bw + tpuh;
let output_rect = output.node_state[LiveTL].pos.get();
let position = if output.is_dummy {
Rect::new_sized_saturating(0, 0, width, height)
} else if let Some((mut x1, mut y1)) = abs_pos {
y1 = y1.clamp_saturating(output_rect.y1() + 1, output_rect.y2());
x1 = x1.clamp_saturating(output_rect.x1() - inner_width + 1, output_rect.x2() - 1);
y1 -= bw + tpuh;
x1 -= bw;
Rect::new_sized_saturating(x1, y1, width, height)
} else {
calculate_float_position(output_rect, width, height)
};
floater.set_ns_requested_visible(ws.float_visible());
floater.set_position(position);
floater.set_ns_child(Some(&child));
floater.set_ns_workspace_type(ws.ty);
floater.pull_child_properties();
{
let dl = &mut *floater.display_link.borrow_mut();
dl.link = Some(dl.stack.stacked.add_last(floater.clone()));
}
floater
.workspace_link
.set(Some(ws.stacked.add_last(floater.clone())));
child.tl_set_parent(floater.clone());
floater.update_effective_visible();
child.tl_restack_popups();
if tl_data.pinned.get() {
floater.toggle_pinned();
}
floater.schedule_title_offsets();
floater.update_icon();
floater
}
fn push_layout_phase(self: &Rc<Self>) {
if !self.layout_phase_scheduled.replace(true) {
self.state.pending_float_layout_phase.push(self.clone());
}
}
fn schedule_layout(self: &Rc<Self>) {
self.layout_scheduled.set(true);
self.push_layout_phase();
}
fn perform_layout(self: &Rc<Self>) {
let ns = &self.node_state[LiveTL];
let Some(child) = ns.child.get() else { return };
let pos = ns.position.get();
let theme = &ns.theme;
let bw = theme.sizes.border_width.get();
let th = theme.sizes.title_height.get();
let tpuh = theme.sizes.title_plus_underline_height.get();
let cpos = Rect::new_sized_saturating(
pos.x1() + bw,
pos.y1() + bw + tpuh,
pos.width() - 2 * bw,
pos.height() - 2 * bw - tpuh,
);
let tr = Rect::new_sized_saturating(bw, bw, pos.width() - 2 * bw, th);
child.clone().tl_change_extents(&cpos);
self.set_ns_title_rect(tr);
self.layout_scheduled.set(false);
self.schedule_render_titles();
self.set_icon_position();
}
fn schedule_render_titles(self: &Rc<Self>) {
self.add_transaction_op(FloatTransactionOp::ScheduleRenderTitles);
}
fn render_title_phase1(&self) -> Rc<AsyncEvent> {
let on_completed = Rc::new(OnDropEvent::default());
let ns = &self.node_state[RenderTL];
let theme = &ns.theme;
let tc = match ns.active.get() {
true => theme.colors.focused_title_text.get(),
false => theme.colors.unfocused_title_text.get(),
};
let font = theme.title_font.get();
let title = self.title.borrow_mut();
let Some(ctx) = self.state.render_ctx.get() else {
return on_completed.event();
};
let scales = self.state.scales.lock();
let tr = ns.title_rect.get();
let tt = &mut *self.title_textures.borrow_mut();
for (scale, _) in scales.iter() {
let tex = tt.get_or_insert_with(*scale, || TextTexture::new(&self.state, &ctx));
let mut th = tr.height();
let mut scalef = None;
let mut width = (tr.width() - ns.offsets.title.get()).max(0);
if *scale != 1 {
let scale = scale.to_f64();
th = (th as f64 * scale).round() as _;
width = (width as f64 * scale).round() as _;
scalef = Some(scale);
}
tex.schedule_render(
on_completed.clone(),
1,
None,
width,
th,
1,
&font,
&title,
tc,
true,
false,
scalef,
);
}
on_completed.event()
}
fn render_title_phase2(&self) {
let ns = &self.node_state[RenderTL];
let theme = &ns.theme;
let th = theme.sizes.title_height.get();
let bw = theme.sizes.border_width.get();
let title = self.title.borrow();
let tt = &*self.title_textures.borrow();
for (_, tt) in tt {
if let Err(e) = tt.flip() {
log::error!("Could not render title {}: {}", title, ErrorFmt(e));
}
}
let pos = ns.position.get();
if ns.visible.get() && pos.width() >= 2 * bw {
let tr =
Rect::new_sized_saturating(pos.x1() + bw, pos.y1() + bw, pos.width() - 2 * bw, th);
self.state.damage(tr);
}
}
fn pointer_move(
self: &Rc<Self>,
id: CursorType,
cursor: &CursorUser,
x: Fixed,
y: Fixed,
target: bool,
) {
let ns = &self.node_state[LiveTL];
let x = x.round_down();
let y = y.round_down();
let theme = &ns.theme;
let bw = theme.sizes.border_width.get();
let tpuh = theme.sizes.title_plus_underline_height.get();
let mut seats = self.cursors.borrow_mut();
let seat_state = seats.entry(id).or_insert_with(|| CursorState {
cursor: KnownCursor::Default,
target,
x,
y,
op_type: OpType::Move,
op_active: false,
dist_hor: 0,
dist_ver: 0,
double_click_state: Default::default(),
});
seat_state.x = x;
seat_state.y = y;
let pos = ns.position.get();
if seat_state.op_active {
let mut x1 = pos.x1();
let mut y1 = pos.y1();
let mut x2 = pos.x2();
let mut y2 = pos.y2();
match seat_state.op_type {
OpType::Move => {
let dx = x - seat_state.dist_hor;
let dy = y - seat_state.dist_ver;
x1 += dx;
y1 += dy;
x2 += dx;
y2 += dy;
}
OpType::ResizeLeft => {
x1 += x - seat_state.dist_hor;
x1 = x1.min(x2 - 2 * bw);
}
OpType::ResizeTop => {
y1 += y - seat_state.dist_ver;
y1 = y1.min(y2 - 2 * bw - tpuh);
}
OpType::ResizeRight => {
x2 += x - pos.width() + seat_state.dist_hor;
x2 = x2.max(x1 + 2 * bw);
}
OpType::ResizeBottom => {
y2 += y - pos.height() + seat_state.dist_ver;
y2 = y2.max(y1 + 2 * bw + tpuh);
}
OpType::ResizeTopLeft => {
x1 += x - seat_state.dist_hor;
y1 += y - seat_state.dist_ver;
x1 = x1.min(x2 - 2 * bw);
y1 = y1.min(y2 - 2 * bw - tpuh);
}
OpType::ResizeTopRight => {
x2 += x - pos.width() + seat_state.dist_hor;
y1 += y - seat_state.dist_ver;
x2 = x2.max(x1 + 2 * bw);
y1 = y1.min(y2 - 2 * bw - tpuh);
}
OpType::ResizeBottomLeft => {
x1 += x - seat_state.dist_hor;
y2 += y - pos.height() + seat_state.dist_ver;
x1 = x1.min(x2 - 2 * bw);
y2 = y2.max(y1 + 2 * bw + tpuh);
}
OpType::ResizeBottomRight => {
x2 += x - pos.width() + seat_state.dist_hor;
y2 += y - pos.height() + seat_state.dist_ver;
x2 = x2.max(x1 + 2 * bw);
y2 = y2.max(y1 + 2 * bw + tpuh);
}
}
let new_pos = Rect::new_saturating(x1, y1, x2, y2);
self.set_position(new_pos);
return;
}
let resize_left = x < bw;
let resize_right = x >= pos.width() - bw;
let resize_top = y < bw;
let resize_bottom = y >= pos.height() - bw;
let id = 0
| ((resize_left as usize) << 0)
| ((resize_right as usize) << 1)
| ((resize_top as usize) << 2)
| ((resize_bottom as usize) << 3);
const OP_TYPES: [OpType; 16] = [
OpType::Move, OpType::ResizeLeft, OpType::ResizeRight, OpType::Move, OpType::ResizeTop, OpType::ResizeTopLeft, OpType::ResizeTopRight, OpType::Move, OpType::ResizeBottom, OpType::ResizeBottomLeft, OpType::ResizeBottomRight, OpType::Move, OpType::Move, OpType::Move, OpType::Move, OpType::Move, ];
let op_type = OP_TYPES[id];
let new_cursor = match op_type {
OpType::Move => KnownCursor::Default,
OpType::ResizeLeft => KnownCursor::EwResize,
OpType::ResizeTop => KnownCursor::NsResize,
OpType::ResizeRight => KnownCursor::EwResize,
OpType::ResizeBottom => KnownCursor::NsResize,
OpType::ResizeTopLeft => KnownCursor::NwResize,
OpType::ResizeTopRight => KnownCursor::NeResize,
OpType::ResizeBottomLeft => KnownCursor::SwResize,
OpType::ResizeBottomRight => KnownCursor::SeResize,
};
seat_state.op_type = op_type;
if new_cursor != mem::replace(&mut seat_state.cursor, new_cursor) {
if seat_state.target {
cursor.set_known(new_cursor);
}
}
}
fn set_workspace_(
self: &Rc<Self>,
ws: &Rc<WorkspaceNode>,
update_pinned: bool,
update_visible: bool,
) {
let ns = &self.node_state[LiveTL];
if let Some(c) = ns.child.get() {
c.tl_set_workspace(ws);
}
self.workspace_link
.set(Some(ws.stacked.add_last(self.clone())));
self.workspace.set(ws.clone());
self.workspace_listener.attach(&ws.listeners);
if ns.workspace_ty.get() != ws.ty {
self.set_ns_workspace_type(ws.ty);
self.schedule_title_offsets();
self.display_link
.borrow_mut()
.restack_on(self.state.float_stack(ws.ty));
if self.node_visible(RenderTL) {
self.state.damage(self.node_absolute_position(RenderTL));
}
}
self.location.set(ws.location());
if update_visible {
self.set_visible(ws.float_visible());
}
if update_pinned && let Some(pl) = &*self.pinned_link.borrow_mut() {
ws.node_state[LiveTL]
.output
.get()
.pinned
.add_last_existing(pl);
}
if let Some(v) = ns.toplevel_icon.get() {
v.set_workspace(ws);
}
}
pub fn set_position(self: &Rc<Self>, pos: Rect) {
self.set_ns_position(pos);
self.schedule_layout();
self.set_icon_position();
}
fn calculate_icon_position(&self) -> (i32, i32) {
let ns = &self.node_state[LiveTL];
let (x, y) = ns.position.get().position();
let (dx, dy) = ns.title_rect.get().position();
let x = x + dx + 1 + ns.offsets.toplevel_icon.get();
let y = y + dy + 1;
(x, y)
}
fn set_icon_position(&self) {
let ns = &self.node_state[LiveTL];
let Some(icon) = ns.toplevel_icon.get() else {
return;
};
let (x, y) = self.calculate_icon_position();
icon.set_position(x, y);
}
pub fn ensure_on_output(self: &Rc<Self>, output: &Rc<OutputNode>) {
if output.is_dummy {
return;
}
let ns = &self.node_state[LiveTL];
let pos = ns.position.get();
let opos = output.node_state[LiveTL].pos.get();
if pos.intersects(&opos) {
return;
}
let bw = ns.theme.sizes.border_width.get();
let th = ns.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_saturating(x1, y1, x2, y2);
self.set_position(new_pos);
}
pub fn move_(self: &Rc<Self>, dx: i32, dy: i32) {
let ns = &self.node_state[LiveTL];
let old_pos = ns.position.get();
let new_pos = old_pos.move_(dx, dy);
self.set_position(new_pos);
}
fn update_child_title(self: &Rc<Self>, title: &str) {
let mut t = self.title.borrow_mut();
if t.deref() != title {
t.clear();
t.push_str(title);
self.schedule_render_titles();
}
}
fn update_child_active(self: &Rc<Self>, active: bool) {
if self.node_state[LiveTL].active.get() != active {
self.set_ns_active(active);
self.schedule_render_titles();
}
}
fn pull_child_properties(self: &Rc<Self>) {
let Some(child) = self.node_state[LiveTL].child.get() else {
return;
};
let data = child.tl_data();
let activation_requested = data.wants_attention.get();
self.set_ns_attention_requested(activation_requested);
if activation_requested {
self.workspace
.get()
.cnode_child_attention_request_changed(&**self, true);
}
self.update_child_title(&data.title.borrow());
self.update_child_active(data.active());
}
fn discard_child_properties(&self) {
if self.node_state[LiveTL].attention_requested.get() {
self.workspace
.get()
.cnode_child_attention_request_changed(self, false);
}
}
fn restack(self: &Rc<Self>) {
let dl = &*self.display_link.borrow();
if let Some(link) = &dl.link {
if link.next().is_none() {
return;
}
let ns = &self.node_state[LiveTL];
if self.node_visible(RenderTL) {
self.state.damage(self.node_absolute_position(RenderTL));
}
dl.restack();
if let Some(tl) = ns.child.get() {
tl.tl_restack_popups();
}
self.state.tree_changed();
}
}
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().node_state[LiveTL].output.get();
Some(output.pinned.add_last(self.clone()))
};
if let Some(tl) = self.node_state[LiveTL].child.get() {
tl.tl_data().pinned.set(pl.is_some());
}
self.set_ns_pinned(pl.is_some());
self.schedule_title_offsets();
}
fn button(
self: Rc<Self>,
id: CursorType,
cursor: &CursorUser,
seat: &Rc<WlSeatGlobal>,
time_usec: u64,
pressed: bool,
) {
let mut cursors = self.cursors.borrow_mut();
let Some(cursor_data) = cursors.get_mut(&id) else {
return;
};
let ns = &self.node_state[LiveTL];
let theme = &ns.theme;
let bw = theme.sizes.border_width.get();
let th = 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 {
Overlay,
Pin,
}
let mut icons = ArrayVec::<FloatIcon, 2>::new();
if ns.workspace_ty.get() == WorkspaceType::Overlay {
icons.push(FloatIcon::Overlay);
}
if theme.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(),
FloatIcon::Overlay => {}
}
}
}
if !cursor_data.op_active {
if !pressed {
return;
}
if cursor_data.op_type == OpType::Move
&& let Some(tl) = ns.child.get()
{
tl.node_do_focus_dyn(seat, Direction::Unspecified);
}
if cursor_data.double_click_state.click(
&self.state,
time_usec,
cursor_data.x,
cursor_data.y,
) && cursor_data.op_type == OpType::Move
&& !is_icon_press
&& let Some(tl) = ns.child.get()
{
drop(cursors);
toplevel_set_floating(&self.state, tl, false);
return;
}
cursor_data.op_active = true;
let pos = ns.position.get();
match cursor_data.op_type {
OpType::Move => {
self.restack();
cursor_data.dist_hor = cursor_data.x;
cursor_data.dist_ver = cursor_data.y;
}
OpType::ResizeLeft => cursor_data.dist_hor = cursor_data.x,
OpType::ResizeTop => cursor_data.dist_ver = cursor_data.y,
OpType::ResizeRight => cursor_data.dist_hor = pos.width() - cursor_data.x,
OpType::ResizeBottom => cursor_data.dist_ver = pos.height() - cursor_data.y,
OpType::ResizeTopLeft => {
cursor_data.dist_hor = cursor_data.x;
cursor_data.dist_ver = cursor_data.y;
}
OpType::ResizeTopRight => {
cursor_data.dist_hor = pos.width() - cursor_data.x;
cursor_data.dist_ver = cursor_data.y;
}
OpType::ResizeBottomLeft => {
cursor_data.dist_hor = cursor_data.x;
cursor_data.dist_ver = pos.height() - cursor_data.y;
}
OpType::ResizeBottomRight => {
cursor_data.dist_hor = pos.width() - cursor_data.x;
cursor_data.dist_ver = pos.height() - cursor_data.y;
}
}
} else if !pressed {
cursor_data.op_active = false;
let ws = cursor.output().ensure_workspace();
self.set_workspace_(&ws, true, true);
}
}
pub fn tile_drag_destination(
self: &Rc<Self>,
source: NodeId,
abs_x: i32,
abs_y: i32,
) -> Option<TileDragDestination> {
let ns = &self.node_state[LiveTL];
let child = ns.child.get()?;
let theme = &ns.theme;
let bw = theme.sizes.border_width.get();
let tpuh = theme.sizes.title_plus_underline_height.get();
let pos = ns.position.get();
let body = Rect::new(
pos.x1() + bw,
pos.y1() + bw + tpuh,
pos.x2() - bw,
pos.y2() - bw,
)?;
child.tl_tile_drag_destination(source, None, body, abs_x, abs_y)
}
fn set_ns_visible(self: &Rc<Self>, v: bool) -> bool {
self.add_transaction_op(FloatTransactionOp::SetVisible(v));
self.node_state[LiveTL].visible.replace(v)
}
fn set_ns_requested_visible(self: &Rc<Self>, v: bool) {
self.add_transaction_op(FloatTransactionOp::SetRequestedVisible(v));
self.node_state[LiveTL].requested_visible.set(v);
}
fn set_ns_position(self: &Rc<Self>, v: Rect) {
self.add_transaction_op(FloatTransactionOp::SetPosition(v));
self.node_state[LiveTL].position.set(v);
}
fn set_ns_child(self: &Rc<Self>, child: Option<&Rc<dyn ToplevelNode>>) {
self.add_transaction_op(FloatTransactionOp::SetChild(child.cloned()));
let is_placeholder = child.is_some_and(|c| c.node_is_placeholder());
self.add_transaction_op(FloatTransactionOp::SetChildIsPlaceholder(is_placeholder));
let ns = &self.node_state[LiveTL];
ns.child.set(child.cloned());
ns.child_is_placeholder.set(is_placeholder);
}
fn set_ns_workspace_type(self: &Rc<Self>, v: WorkspaceType) {
self.add_transaction_op(FloatTransactionOp::SetWorkspaceType(v));
self.node_state[LiveTL].workspace_ty.set(v);
}
fn set_ns_title_rect(self: &Rc<Self>, v: Rect) {
self.add_transaction_op(FloatTransactionOp::SetTitleRect(v));
self.node_state[LiveTL].title_rect.set(v);
}
fn set_ns_active(self: &Rc<Self>, v: bool) {
self.add_transaction_op(FloatTransactionOp::SetActive(v));
self.node_state[LiveTL].active.set(v);
}
fn set_ns_attention_requested(self: &Rc<Self>, v: bool) {
self.add_transaction_op(FloatTransactionOp::SetAttentionRequested(v));
self.node_state[LiveTL].attention_requested.set(v);
}
fn set_ns_pinned(self: &Rc<Self>, v: bool) {
self.add_transaction_op(FloatTransactionOp::SetPinned(v));
self.node_state[LiveTL].pinned.set(v);
}
fn compute_theme(&self, data: &ToplevelData) -> FloatTheme {
let state = &self.state;
let theme = &state.theme;
define_ident_mut!(theme.colors.@focused_title_text.val.get());
define_ident_mut!(theme.colors.@unfocused_title_text.val.get());
define_ident_mut!(theme.colors.@border.val.get());
define_ident_mut!(theme.colors.@focused_title_background.val.get());
define_ident_mut!(theme.colors.@attention_requested_background.val.get());
define_ident_mut!(theme.colors.@unfocused_title_background.val.get());
define_ident_mut!(theme.colors.@separator.val.get());
define_ident_mut!(theme.colors.@focused_border.get_opt());
define_ident_mut!(theme.sizes.@border_width.val.get());
define_ident_mut!(theme.sizes.@title_height.val.get());
define_ident_mut!(theme.@show_titles.get());
define_ident_mut!(theme.@show_window_icons.get());
define_ident_mut!(theme.@window_icons_grayscale.get());
define_ident_mut!(theme.@title_font());
if let Some(theme) = data.theme(ParentTheme) {
write_ident_opt!(theme.colors.@focused_title_text.get());
write_ident_opt!(theme.colors.@unfocused_title_text.get());
write_ident_opt!(theme.colors.@border.get());
write_ident_opt!(theme.colors.@focused_title_background.get());
write_ident_opt!(theme.colors.@attention_requested_background.get());
write_ident_opt!(theme.colors.@unfocused_title_background.get());
write_ident_opt!(theme.colors.@separator.get());
write_ident_opt!(theme.sizes.@border_width.get());
write_ident_opt!(theme.sizes.@title_height.get());
write_ident_opt!(theme.@show_titles.get());
write_ident_opt!(theme.@show_window_icons.get());
write_ident_opt!(theme.@window_icons_grayscale.get());
write_ident_opt!(theme.@title_font.get());
write_ident_or!(theme.colors.@focused_border.get());
}
define_ident!(Cell::new(compute_focused_border(@focused_border, border)));
define_ident!(Cell::new(@border));
define_ident!(Cell::new(@title_plus_underline_height(show_titles, title_height)));
define_ident!(Cell::new(@title_underline_height(show_titles)));
define_ident!(Cell::new(@title_icon_size(show_titles, show_window_icons, title_height)));
define_ident!(Cell::new(compute_title_height(show_titles, @title_height)));
define_ident!(Cell::new(state.@show_pin_icon.get()));
define_ident!(Cell::new(@focused_title_text));
define_ident!(Cell::new(@unfocused_title_text));
define_ident!(Cell::new(@focused_title_background));
define_ident!(Cell::new(@attention_requested_background));
define_ident!(Cell::new(@unfocused_title_background));
define_ident!(Cell::new(@separator));
define_ident!(Cell::new(@border_width));
define_ident!(Cell::new(@window_icons_grayscale));
define_ident!(CloneCell::new(@title_font));
FloatTheme {
colors: FloatThemeColors {
focused_title_text,
unfocused_title_text,
focused_border,
border,
focused_title_background,
attention_requested_background,
unfocused_title_background,
separator,
},
sizes: FloatThemeSizes {
border_width,
title_height,
title_plus_underline_height,
title_underline_height,
title_icon_size,
},
show_pin_icon,
window_icons_grayscale,
title_font,
}
}
fn set_ns_toplevel_icon(self: &Rc<Self>, v: Option<IconSurface>) -> Option<IconSurface> {
self.add_transaction_op(FloatTransactionOp::SetToplevelIcon(v.clone()));
let old = self.node_state[LiveTL].toplevel_icon.set(v);
if let Some(v) = &old {
v.disown();
}
old
}
fn schedule_title_offsets(self: &Rc<Self>) {
self.title_offsets_scheduled.set(true);
self.push_layout_phase();
}
fn update_title_offsets(self: &Rc<Self>) {
self.title_offsets_scheduled.set(false);
let ns = &self.node_state[LiveTL];
let theme = &ns.theme;
let th = theme.sizes.title_height.get();
let mut x = 0;
macro_rules! snapshot {
($x:ident) => {
let $x = Cell::new(x);
};
}
snapshot!(overlay_icon);
if ns.workspace_ty.get() == WorkspaceType::Overlay {
x += th;
}
snapshot!(pin_icon);
if ns.pinned.get() || theme.show_pin_icon.get() {
x += th;
}
snapshot!(toplevel_icon);
if ns.toplevel_icon.is_some() {
x += th;
}
snapshot!(title);
let offsets = Offsets {
overlay_icon,
pin_icon,
toplevel_icon,
title,
};
let changed = ns.offsets.cached_update(offsets, |op| {
self.add_transaction_op(FloatTransactionOp::SetOffset(op));
});
let OffsetsChanged {
overlay_icon,
pin_icon,
toplevel_icon,
title,
} = changed;
if title {
self.schedule_render_titles();
}
let damage = or_chain!(________)
|| overlay_icon
|| pin_icon
|| toplevel_icon
|| title
|| or_chain!();
if damage && self.node_visible(LiveTL) {
self.state
.schedule_damage(self.node_absolute_position(LiveTL));
}
if toplevel_icon {
self.set_icon_position();
}
}
fn update_icon(self: &Rc<Self>) {
let ns = &self.node_state[LiveTL];
let theme = &ns.theme;
let size = theme.sizes.title_icon_size.get();
let surface = (size > 0)
.and_then(|| ns.child.get())
.and_then(|c| c.tl_icon_surface_factory())
.map(|n| n.build_surface(self, self.id));
if let Some(surface) = &surface {
let (x, y) = self.calculate_icon_position();
surface.set_grayscale(theme.window_icons_grayscale.get());
surface.set_position(x, y);
surface.set_size(size, size);
surface.set_visible(ns.visible.get());
surface.set_workspace(&self.workspace.get());
}
let new = surface.as_ref().is_some();
let old = self.set_ns_toplevel_icon(surface).is_some();
if old != new {
self.schedule_title_offsets();
}
}
}
impl Debug for FloatNode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FloatNode").finish_non_exhaustive()
}
}
impl NodeBase for FloatNode {
fn node_id(&self) -> NodeId {
self.id.into()
}
fn node_seat_state(&self) -> &NodeSeatState {
&self.seat_state
}
fn node_visit(self: &Rc<Self>, visitor: &mut dyn NodeVisitor) {
visitor.visit_float(&self);
}
fn node_visit_children(&self, visitor: &mut dyn NodeVisitor) {
if let Some(c) = self.node_state[LiveTL].child.get() {
c.node_visit_dyn(visitor);
}
}
fn node_visible(&self, tl: TreeTimeline) -> bool {
self.node_state[tl].visible.get()
}
fn node_absolute_position(&self, tl: TreeTimeline) -> Rect {
self.node_state[tl].position.get()
}
fn node_output(&self) -> Option<Rc<OutputNode>> {
Some(self.workspace.get().node_state[LiveTL].output.get())
}
fn node_workspace(&self) -> Option<Rc<WorkspaceNode>> {
Some(self.workspace.get())
}
fn node_location(&self) -> Option<NodeLocation> {
Some(self.location.get())
}
fn node_layer(&self) -> NodeLayerLink {
let Some(l) = self.display_link.borrow().link.as_ref().map(|l| l.to_ref()) else {
return NodeLayerLink::Display;
};
match self.node_state[LiveTL].workspace_ty.get() {
WorkspaceType::Normal => NodeLayerLink::Stacked(l),
WorkspaceType::Overlay => NodeLayerLink::OverlayStacked(l),
}
}
fn node_debugfs(self: Rc<Self>) -> FuseInodeWithKey {
self.debugfs()
}
fn node_child_title_changed(self: Rc<Self>, _child: &dyn Node, title: &str) {
self.update_child_title(title);
}
fn node_accepts_focus(&self) -> bool {
if let Some(c) = self.node_state[LiveTL].child.get() {
return c.tl_accepts_keyboard_focus();
}
false
}
fn node_do_focus(self: &Rc<Self>, seat: &Rc<WlSeatGlobal>, direction: Direction) {
if let Some(c) = self.node_state[LiveTL].child.get() {
c.node_do_focus_dyn(seat, direction);
}
}
fn node_find_tree_at(
&self,
x: i32,
y: i32,
tree: &mut Vec<FoundNode>,
usecase: FindTreeUsecase,
) -> FindTreeResult {
let ns = &self.node_state[LiveTL];
let theme = &ns.theme;
let tpuh = theme.sizes.title_plus_underline_height.get();
let bw = theme.sizes.border_width.get();
let pos = ns.position.get();
if x < bw || x >= pos.width() - bw {
return FindTreeResult::AcceptsInput;
}
if y < bw + tpuh || y >= pos.height() - bw {
return FindTreeResult::AcceptsInput;
}
let Some(child) = ns.child.get() else {
return FindTreeResult::Other;
};
let x = x - bw;
let y = y - bw - tpuh;
tree.push(FoundNode {
node: child.clone(),
x,
y,
});
child.node_find_tree_at(x, y, tree, usecase)
}
fn node_child_active_changed(self: Rc<Self>, _child: &dyn Node, active: bool, _depth: u32) {
self.update_child_active(active);
}
fn node_render(&self, renderer: &mut Renderer<'_>, x: i32, y: i32, _bounds: Option<&Rect>) {
renderer.render_floating(self, x, y);
}
fn node_make_visible(self: &Rc<Self>) {
if self.node_state[LiveTL].visible.get() {
return;
}
self.workspace.get().cnode_make_visible(&**self);
}
fn node_grab_workspace_changed(
self: Rc<Self>,
_seat: &Rc<WlSeatGlobal>,
output: &Rc<OutputNode>,
ws: Option<&Rc<WorkspaceNode>>,
_reason: WorkspaceChangeReason,
) {
if ws.map(|ws| ws.id) == Some(self.workspace.get().id) {
return;
}
let ws = ws.cloned().unwrap_or_else(|| output.ensure_workspace());
self.set_workspace_(&ws, true, false);
}
fn node_on_button(
self: Rc<Self>,
seat: &Rc<WlSeatGlobal>,
time_usec: u64,
button: u32,
state: ButtonState,
_serial: u64,
) {
if button == BTN_RIGHT && state == ButtonState::Pressed {
self.toggle_pinned();
}
if button != BTN_LEFT {
return;
}
self.button(
CursorType::Seat(seat.id()),
seat.pointer_cursor(),
seat,
time_usec,
state == ButtonState::Pressed,
);
}
fn node_on_pointer_enter(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
self.pointer_move(
CursorType::Seat(seat.id()),
seat.pointer_cursor(),
x,
y,
false,
);
}
fn node_on_pointer_unfocus(&self, seat: &Rc<WlSeatGlobal>) {
let mut cursors = self.cursors.borrow_mut();
let id = CursorType::Seat(seat.id());
if let Some(seat_state) = cursors.get_mut(&id) {
seat_state.target = false;
}
}
fn node_on_pointer_focus(&self, seat: &Rc<WlSeatGlobal>) {
let mut cursors = self.cursors.borrow_mut();
let id = CursorType::Seat(seat.id());
if let Some(seat_state) = cursors.get_mut(&id) {
seat_state.target = true;
seat.pointer_cursor().set_known(seat_state.cursor);
}
}
fn node_on_pointer_motion(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
self.pointer_move(
CursorType::Seat(seat.id()),
seat.pointer_cursor(),
x,
y,
false,
);
}
fn node_on_tablet_tool_leave(&self, tool: &Rc<TabletTool>, _time_usec: u64) {
let id = CursorType::TabletTool(tool.id);
self.cursors.borrow_mut().remove(&id);
}
fn node_on_tablet_tool_enter(
self: Rc<Self>,
tool: &Rc<TabletTool>,
_time_usec: u64,
x: Fixed,
y: Fixed,
) {
tool.cursor().set_known(KnownCursor::Default);
self.pointer_move(CursorType::TabletTool(tool.id), tool.cursor(), x, y, true);
}
fn node_on_tablet_tool_apply_changes(
self: Rc<Self>,
tool: &Rc<TabletTool>,
time_usec: u64,
changes: Option<&TabletToolChanges>,
x: Fixed,
y: Fixed,
) {
self.pointer_move(CursorType::TabletTool(tool.id), tool.cursor(), x, y, false);
if let Some(changes) = changes
&& let Some(pressed) = changes.down
{
self.button(
CursorType::TabletTool(tool.id),
tool.cursor(),
tool.seat(),
time_usec,
pressed,
);
}
}
fn node_into_float(self: Rc<Self>) -> Option<Rc<FloatNode>> {
Some(self.clone())
}
fn node_into_containing_node(self: Rc<Self>) -> Option<Rc<dyn ContainingNode>> {
Some(self)
}
fn node_is_float(&self) -> bool {
true
}
}
impl ContainingNode for FloatNode {
fn cnode_replace_child(self: Rc<Self>, _old: &dyn Node, new: Rc<dyn ToplevelNode>) {
self.discard_child_properties();
self.set_ns_child(Some(&new));
new.tl_set_parent(self.clone());
self.pull_child_properties();
self.update_effective_visible();
self.schedule_layout();
self.update_icon();
ThemeChangeListener::changed(self);
}
fn cnode_remove_child2(self: Rc<Self>, _child: &dyn Node, _preserve_focus: bool) {
self.discard_child_properties();
self.set_ns_visible(false);
self.set_ns_child(None);
self.add_transaction_op(FloatTransactionOp::ClearLink);
self.workspace_link.set(None);
self.pinned_link.take();
self.set_ns_pinned(false);
self.set_ns_toplevel_icon(None);
}
fn cnode_accepts_child(&self, _node: &dyn Node) -> bool {
true
}
fn cnode_child_attention_request_changed(self: Rc<Self>, _node: &dyn Node, set: bool) {
if self.node_state[LiveTL].attention_requested.get() != set {
self.set_ns_attention_requested(set);
self.workspace
.get()
.cnode_child_attention_request_changed(&*self, set);
}
}
fn cnode_workspace(self: Rc<Self>) -> Rc<WorkspaceNode> {
self.workspace.get()
}
fn cnode_make_visible(self: Rc<Self>, _child: &dyn Node) {
self.node_make_visible();
}
fn cnode_set_child_position(self: Rc<Self>, _child: &dyn Node, x: i32, y: i32) {
let ns = &self.node_state[LiveTL];
let theme = &ns.theme;
let tpuh = theme.sizes.title_plus_underline_height.get();
let bw = theme.sizes.border_width.get();
let (x, y) = (x - bw, y - tpuh - bw);
let pos = ns.position.get();
if pos.position() != (x, y) {
let new_pos = pos.at_point(x, y);
self.set_position(new_pos);
}
}
fn cnode_resize_child(
self: Rc<Self>,
_child: &dyn Node,
new_x1: Option<i32>,
new_y1: Option<i32>,
new_x2: Option<i32>,
new_y2: Option<i32>,
) {
let ns = &self.node_state[LiveTL];
let theme = &ns.theme;
let tpuh = theme.sizes.title_plus_underline_height.get();
let bw = theme.sizes.border_width.get();
let pos = ns.position.get();
let mut x1 = pos.x1();
let mut x2 = pos.x2();
let mut y1 = pos.y1();
let mut y2 = pos.y2();
if let Some(v) = new_x1 {
x1 = (v - bw).min(x2 - bw - bw);
}
if let Some(v) = new_x2 {
x2 = (v + bw).max(x1 + bw + bw);
}
if let Some(v) = new_y1 {
y1 = (v - tpuh - bw).min(y2 - bw - tpuh - bw);
}
if let Some(v) = new_y2 {
y2 = (v + bw).max(y1 + bw + tpuh + bw);
}
let new_pos = Rect::new_saturating(x1, y1, x2, y2);
if new_pos != pos {
self.set_position(new_pos);
}
}
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();
}
fn cnode_get_float(self: Rc<Self>) -> Option<Rc<FloatNode>> {
Some(self)
}
fn cnode_child_icon_factory_changed(self: Rc<Self>, _child: NodeId) {
self.update_icon();
}
fn cnode_child_theme_changed(self: Rc<Self>, _child: NodeId) {
ThemeChangeListener::changed(self);
}
}
impl FloatNode {
fn update_effective_visible(self: &Rc<Self>) {
let ns = &self.node_state[LiveTL];
let visible = ns.requested_visible.get() && !ns.child_is_placeholder.get();
if self.set_ns_visible(visible) != visible {
self.add_transaction_op(FloatTransactionOp::Damage);
if visible {
self.display_link.borrow().invalidate();
}
if let Some(v) = ns.toplevel_icon.get() {
v.set_visible(visible);
}
}
if let Some(child) = ns.child.get() {
child.tl_set_visible(visible);
}
self.seat_state.set_visible(&**self, visible);
}
fn set_visible(self: &Rc<Self>, visible: bool) {
self.set_ns_requested_visible(visible);
self.update_effective_visible();
}
}
impl StackedNode for FloatNode {
fn stacked_set_visible(self: Rc<Self>, visible: bool) {
self.set_visible(visible);
}
fn stacked_has_workspace_link(&self) -> bool {
true
}
fn stacked_validate(self: Rc<Self>, tl: TreeTimeline) {
if self.node_state[tl].visible.get() {
self.display_link.borrow_mut().add_last_visible(&self, tl);
}
}
fn stacked_add_stack_op(self: Rc<Self>, op: NodeStackTransactionOp) {
self.add_transaction_op(FloatTransactionOp::NodeStack(op));
}
}
impl PinnedNode for FloatNode {
fn set_workspace(self: Rc<Self>, workspace: &Rc<WorkspaceNode>, update_visible: bool) {
self.set_workspace_(workspace, false, update_visible);
}
}
impl WorkspaceEventListener for FloatNode {
fn output_changed(
self: Rc<Self>,
ws: &Rc<WorkspaceNode>,
_old: &Rc<OutputNode>,
new: &Rc<OutputNode>,
) {
if ws.ty == WorkspaceType::Normal
&& let Some(pinned) = &*self.pinned_link.borrow()
{
new.pinned.add_last_existing(pinned);
}
}
}
impl ThemeChangeListener for FloatNode {
fn changed(self: Rc<Self>) {
let ns = &self.node_state[LiveTL];
let Some(child) = ns.child.get() else {
return;
};
let theme = self.compute_theme(child.tl_data());
let changed = ns.theme.cached_update(theme, |op| {
self.add_transaction_op(FloatTransactionOp::ThemeOp(op));
});
let FloatThemeChanged {
colors:
FloatThemeColorsChanged {
focused_title_text,
unfocused_title_text,
focused_border,
border,
focused_title_background,
attention_requested_background,
unfocused_title_background,
separator,
},
sizes:
FloatThemeSizesChanged {
border_width,
title_height,
title_plus_underline_height,
title_underline_height,
title_icon_size,
},
show_pin_icon,
window_icons_grayscale,
title_font,
} = changed;
if title_icon_size {
let icon_size = ns.theme.sizes.title_icon_size.get();
if icon_size <= 0 {
self.set_ns_toplevel_icon(None);
} else if let Some(v) = ns.toplevel_icon.get() {
v.set_size(icon_size, icon_size);
} else {
self.update_icon();
}
}
let layout = or_chain!()
|| border_width
|| title_height
|| title_plus_underline_height
|| title_underline_height
|| or_chain!();
if layout {
self.schedule_layout();
}
let title = or_chain!(___________)
|| focused_title_text
|| unfocused_title_text
|| title_font
|| or_chain!();
if title {
self.schedule_render_titles();
}
let damage = or_chain!()
|| layout
|| window_icons_grayscale
|| focused_border
|| border
|| focused_title_background
|| attention_requested_background
|| unfocused_title_background
|| separator
|| or_chain!();
if damage && self.node_visible(LiveTL) {
self.state
.schedule_damage(self.node_absolute_position(LiveTL));
}
let title_offsets = or_chain!(__________)
|| title_height
|| title_icon_size
|| show_pin_icon
|| or_chain!();
if title_offsets {
self.schedule_title_offsets();
}
if window_icons_grayscale && let Some(icon) = ns.toplevel_icon.get() {
let grayscale = ns.theme.window_icons_grayscale.get();
icon.set_grayscale(grayscale);
}
let title_icons = or_chain!()
|| title_height
|| unfocused_title_background
|| attention_requested_background
|| focused_title_background
|| or_chain!();
if title_icons {
self.add_transaction_op(FloatTransactionOp::UpdateTitleIcons);
}
}
}
impl GfxCtxChangedListener for FloatNode {
fn handle_gfx_context_change(self: Rc<Self>) {
self.title_textures.borrow_mut().clear();
self.schedule_render_titles();
self.schedule_title_offsets();
}
}
impl ScalesChangedListener for FloatNode {
fn changed(self: Rc<Self>) {
self.title_textures.borrow_mut().clear();
self.schedule_render_titles();
}
}
impl dyn Node {
pub fn node_restack(self: &Rc<Self>) {
if let Some(tl) = self.clone().node_toplevel()
&& let Some(float) = tl.tl_data().float.get()
{
float.restack();
}
}
}
pub enum FloatTransactionOp {
SetVisible(bool),
SetRequestedVisible(bool),
SetPosition(Rect),
SetChild(Option<Rc<dyn ToplevelNode>>),
SetChildIsPlaceholder(bool),
SetWorkspaceType(WorkspaceType),
SetTitleRect(Rect),
SetActive(bool),
SetAttentionRequested(bool),
SetPinned(bool),
NodeStack(NodeStackTransactionOp),
Damage,
ScheduleRenderTitles,
ClearLink,
ThemeOp(FloatThemeOp),
SetOffset(OffsetsOp),
SetToplevelIcon(Option<IconSurface>),
UpdateTitleIcons,
}
impl Transactionable for FloatNode {
type T = FloatTransactionOp;
fn data(&self) -> &TransactionData<Self::T> {
&self.transaction_data
}
fn apply(self: &Rc<Self>, op: Self::T) {
let s = &self.node_state[RenderTL];
let dmg = |r| self.state.damage(r);
let dmg_rel = |r: Rect| {
let (x, y) = s.position.get().position();
dmg(r.move_(x, y));
};
match op {
FloatTransactionOp::SetVisible(v) => {
if s.visible.replace(v) != v {
dmg(s.position.get());
}
}
FloatTransactionOp::SetPosition(v) => {
let old = s.position.replace(v);
if s.visible.get() && old != v {
dmg(old);
dmg(v);
}
}
FloatTransactionOp::SetRequestedVisible(v) => {
s.requested_visible.set(v);
}
FloatTransactionOp::SetChild(v) => {
s.child.set(v);
if s.visible.get() {
dmg(s.position.get());
}
}
FloatTransactionOp::SetChildIsPlaceholder(v) => {
s.child_is_placeholder.set(v);
}
FloatTransactionOp::Damage => {
dmg(s.position.get());
}
FloatTransactionOp::ScheduleRenderTitles => {
if !self.render_titles_scheduled.replace(true) {
self.state.pending_float_titles.push(self.clone());
}
}
FloatTransactionOp::SetWorkspaceType(v) => {
if s.workspace_ty.replace(v) != v {
dmg_rel(s.title_rect.get());
}
}
FloatTransactionOp::SetTitleRect(v) => {
let old = s.title_rect.replace(v);
if old != v {
dmg_rel(old);
dmg_rel(v);
}
}
FloatTransactionOp::SetActive(v) => {
if s.active.replace(v) != v {
dmg(s.position.get());
}
}
FloatTransactionOp::SetAttentionRequested(v) => {
if s.attention_requested.replace(v) != v {
dmg_rel(s.title_rect.get());
}
}
FloatTransactionOp::SetPinned(v) => {
if s.pinned.replace(v) != v {
dmg_rel(s.title_rect.get());
}
}
FloatTransactionOp::NodeStack(v) => {
self.display_link.borrow_mut().run_op(v);
}
FloatTransactionOp::ClearLink => {
self.display_link.borrow_mut().clear();
}
FloatTransactionOp::ThemeOp(v) => {
s.theme.cached_apply(v);
}
FloatTransactionOp::SetOffset(v) => {
s.offsets.cached_apply(v);
}
FloatTransactionOp::SetToplevelIcon(v) => {
s.toplevel_icon.set(v);
}
FloatTransactionOp::UpdateTitleIcons => {
let theme = &s.theme;
define_ident!(theme.sizes.@title_height.get());
define_ident!(theme.colors.@unfocused_title_background.get());
define_ident!(theme.colors.@focused_title_background.get());
define_ident!(theme.colors.@attention_requested_background.get());
let key = TitleIconKey {
title_height,
unfocused_title_background,
focused_title_background,
attention_requested_background,
focused_inactive_title_background: focused_title_background,
};
self.title_icons.set_key(key);
}
}
}
}
pub fn calculate_float_position(output_rect: Rect, mut width: i32, mut height: i32) -> Rect {
let mut x1 = output_rect.x1();
let mut y1 = output_rect.y1();
if width < output_rect.width() {
x1 += (output_rect.width() - width) / 2;
} else {
width = output_rect.width();
}
if height < output_rect.height() {
y1 += (output_rect.height() - height) / 2;
} else {
height = output_rect.height();
}
Rect::new_sized_saturating(x1, y1, width, height)
}
impl IconSurfaceOwner for FloatNode {
fn destroyed(self: Rc<Self>, _node_id: NodeId) {
self.set_ns_toplevel_icon(None);
self.schedule_title_offsets();
}
}