Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 158 additions & 77 deletions src/shell/element/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ impl fmt::Debug for CosmicStack {
#[derive(Debug, Clone)]
pub struct CosmicStackInternal {
windows: Arc<Mutex<Vec<CosmicSurface>>>,
tab_models: Arc<[tab::Model]>,
active: Arc<AtomicUsize>,
activated: Arc<AtomicBool>,
group_focused: Arc<AtomicBool>,
Expand All @@ -107,6 +108,77 @@ pub struct CosmicStackInternal {
}

impl CosmicStackInternal {
pub fn set_previous_index(&self, moved_into: Option<&Seat<State>>) -> Option<usize> {
let last_mod_serial = moved_into.and_then(|seat| seat.last_modifier_change());
let mut prev_idx = self.previous_index.lock().unwrap();
if !prev_idx.is_some_and(|(serial, _)| Some(serial) == last_mod_serial) {
*prev_idx = last_mod_serial.map(|s| (s, self.active.load(Ordering::SeqCst)));
}
prev_idx.map(|(_, idx)| idx)
}

#[must_use]
pub fn add_window(&self, idx: Option<usize>, window: CosmicSurface) -> Message {
window.send_configure();
self.scroll_to_focus.store(true, Ordering::SeqCst);
let model = tab::Model::from(&window);
let mut windows = self.windows.lock().unwrap();
if let Some(idx) = idx {
windows.insert(idx, window);
let prev_active = self.active.swap(idx, Ordering::SeqCst);
if prev_active == idx {
self.reenter.store(true, Ordering::SeqCst);
self.previous_keyboard.store(prev_active, Ordering::SeqCst);
}

Message::TabInsert(idx, model)
} else {
windows.push(window);
self.active.store(windows.len() - 1, Ordering::SeqCst);
Message::TabAdd(model)
}
}

#[must_use]
pub fn remove_window(&self, idx: usize) -> Option<CosmicSurface> {
let mut windows = self.windows.lock().unwrap();

if windows.len() == 1 {
self.override_alive.store(false, Ordering::SeqCst);
let window = &windows[0];
window.try_force_undecorated(false);
window.set_tiled(false);
return None;
}

if windows.len() <= idx {
return None;
}

if idx == self.active.load(Ordering::SeqCst) {
self.reenter.store(true, Ordering::SeqCst);
}

let window = windows.remove(idx);
window.try_force_undecorated(false);
window.set_tiled(false);

_ = self
.active
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |active| {
if active == idx {
self.scroll_to_focus.store(true, Ordering::SeqCst);
Some(windows.len() - 1)
} else if idx < active {
Some(active - 1)
} else {
None
}
});

Some(window)
}

pub fn swap_focus(&self, focus: Option<Focus>) -> Option<Focus> {
let value = focus.map_or(0, |x| x as u8);
unsafe { Focus::from_u8(self.pointer_entered.swap(value, Ordering::SeqCst)) }
Expand All @@ -132,7 +204,11 @@ impl CosmicStack {
handle: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme,
) -> CosmicStack {
let windows = windows.map(Into::into).collect::<Vec<_>>();
let (tab_models, windows) = windows
.map(Into::into)
.map(|window| (tab::Model::from(&window), window))
.collect::<(Vec<_>, Vec<_>)>();

assert!(!windows.is_empty());

for window in &windows {
Expand All @@ -145,6 +221,7 @@ impl CosmicStack {
CosmicStack(IcedElement::new(
CosmicStackInternal {
windows: Arc::new(Mutex::new(windows)),
tab_models: Arc::from(tab_models),
active: Arc::new(AtomicUsize::new(0)),
activated: Arc::new(AtomicBool::new(false)),
group_focused: Arc::new(AtomicBool::new(false)),
Expand Down Expand Up @@ -173,90 +250,59 @@ impl CosmicStack {
let window = window.into();
window.try_force_undecorated(true);
window.set_tiled(true);
self.0.with_program(|p| {
let last_mod_serial = moved_into.and_then(|seat| seat.last_modifier_change());
let mut prev_idx = p.previous_index.lock().unwrap();
if !prev_idx.is_some_and(|(serial, _)| Some(serial) == last_mod_serial) {
*prev_idx = last_mod_serial.map(|s| (s, p.active.load(Ordering::SeqCst)));
}
let message = self.0.with_program(|p| {
p.set_previous_index(moved_into);

if let Some(mut geo) = p.geometry.lock().unwrap().clone() {
geo.loc.y += TAB_HEIGHT;
geo.size.h -= TAB_HEIGHT;
window.set_geometry(geo, TAB_HEIGHT as u32);
}
window.send_configure();
if let Some(idx) = idx {
p.windows.lock().unwrap().insert(idx, window);
let old_idx = p.active.swap(idx, Ordering::SeqCst);
if old_idx == idx {
p.reenter.store(true, Ordering::SeqCst);
p.previous_keyboard.store(old_idx, Ordering::SeqCst);
}
} else {
let mut windows = p.windows.lock().unwrap();
windows.push(window);
p.active.store(windows.len() - 1, Ordering::SeqCst);
}
p.scroll_to_focus.store(true, Ordering::SeqCst);

p.add_window(idx, window)
});

self.0.queue_message(message);
self.0
.resize(Size::from((self.active().geometry().size.w, TAB_HEIGHT)));
self.0.force_redraw()
}

pub fn remove_window(&self, window: &CosmicSurface) {
self.0.with_program(|p| {
let mut windows = p.windows.lock().unwrap();
if windows.len() == 1 {
p.override_alive.store(false, Ordering::SeqCst);
let window = windows.get(0).unwrap();
window.try_force_undecorated(false);
window.set_tiled(false);
return;
}
let message = self.0.with_program(|p| {
let windows = p.windows.lock().unwrap();
let idx = windows.iter().position(|w| w == window)?;
drop(windows);
p.remove_window(idx)
.is_some()
.then(|| Message::TabRemove(idx))
});

let Some(idx) = windows.iter().position(|w| w == window) else {
return;
};
if idx == p.active.load(Ordering::SeqCst) {
p.reenter.store(true, Ordering::SeqCst);
}
let window = windows.remove(idx);
window.try_force_undecorated(false);
window.set_tiled(false);
if let Some(message) = message {
self.0.queue_message(message);
}

p.active.fetch_min(windows.len() - 1, Ordering::SeqCst);
});
self.0
.resize(Size::from((self.active().geometry().size.w, TAB_HEIGHT)));
self.0.force_redraw()
}

pub fn remove_idx(&self, idx: usize) -> Option<CosmicSurface> {
let window = self.0.with_program(|p| {
let mut windows = p.windows.lock().unwrap();
if windows.len() == 1 {
p.override_alive.store(false, Ordering::SeqCst);
let window = windows.get(0).unwrap();
window.try_force_undecorated(false);
window.set_tiled(false);
return Some(window.clone());
}
if windows.len() <= idx {
return None;
}
if idx == p.active.load(Ordering::SeqCst) {
p.reenter.store(true, Ordering::SeqCst);
let (message, window) = self.0.with_program(|p| match p.remove_window(idx) {
Some(window) => (Some(Message::TabRemove(idx)), Some(window)),
None => {
let windows = p.windows.lock().unwrap();
if windows.len() == 1 {
(None, Some(windows[0].clone()))
} else {
(None, None)
}
}
let window = windows.remove(idx);
window.try_force_undecorated(false);
window.set_tiled(false);
});

p.active.fetch_min(windows.len() - 1, Ordering::SeqCst);
if let Some(message) = message {
self.0.queue_message(message);
}

Some(window)
});
self.0
.resize(Size::from((self.active().geometry().size.w, TAB_HEIGHT)));
self.0.force_redraw();
Expand Down Expand Up @@ -438,6 +484,7 @@ impl CosmicStack {
});

if !matches!(result, MoveResult::Default) {
self.0.queue_message(Message::Refresh);
self.0
.resize(Size::from((self.active().geometry().size.w, TAB_HEIGHT)));
}
Expand Down Expand Up @@ -787,14 +834,19 @@ impl CosmicStack {
}
}

#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub enum Message {
DragStart,
Menu,
TabAdd(tab::Model),
TabMenu(usize),
TabInsert(usize, tab::Model),
TabRemove(usize),
TabSwap(usize, usize),
PotentialTabDragStart(usize),
Activate(usize),
Close(usize),
Refresh,
ScrollForward,
ScrollBack,
Scrolled,
Expand Down Expand Up @@ -842,6 +894,15 @@ impl Program for CosmicStackInternal {
last_seat: Option<&(Seat<State>, Serial)>,
) -> Task<Self::Message> {
match message {
Message::Refresh => {
self.tab_models = self
.windows
.lock()
.unwrap()
.iter()
.map(tab::Model::from)
.collect();
}
Message::DragStart => {
if let Some((seat, serial)) = last_seat.cloned() {
let active = self.active.load(Ordering::SeqCst);
Expand Down Expand Up @@ -952,6 +1013,28 @@ impl Program for CosmicStackInternal {
}
}
}
Message::TabAdd(model) => {
let mut tab_models = Vec::with_capacity(self.tab_models.len() + 1);
tab_models.extend_from_slice(&self.tab_models);
tab_models.push(model);
self.tab_models = tab_models.into()
}
Message::TabInsert(idx, model) => {
let mut tab_models = Vec::with_capacity(self.tab_models.len() + 1);
tab_models.extend_from_slice(&self.tab_models);
tab_models.insert(idx, model);
self.tab_models = tab_models.into()
}
Message::TabRemove(idx) => {
let mut tab_models = Vec::from(self.tab_models.as_ref());
_ = tab_models.remove(idx);
self.tab_models = tab_models.into();
}
Message::TabSwap(from, to) => {
let mut tab_models = Vec::from(self.tab_models.as_ref());
tab_models.swap(from, to);
self.tab_models = tab_models.into();
}
Message::TabMenu(idx) => {
if let Some((seat, serial)) = last_seat.cloned() {
if let Some(surface) = self.windows.lock().unwrap()[idx]
Expand Down Expand Up @@ -1001,12 +1084,14 @@ impl Program for CosmicStackInternal {
}

fn view(&self) -> CosmicElement<'_, Self::Message> {
let windows = self.windows.lock().unwrap();
if self.geometry.lock().unwrap().is_none() {
return iced_widget::row(Vec::new()).into();
};

let active = self.active.load(Ordering::SeqCst);
let activated = self.activated.load(Ordering::SeqCst);
let group_focused = self.group_focused.load(Ordering::SeqCst);
let maximized = self.windows.lock().unwrap()[active].is_maximized(false);

let elements = vec![
cosmic_widget::icon::from_name("window-stack-symbolic")
Expand All @@ -1033,20 +1118,14 @@ impl Program for CosmicStackInternal {
.into(),
CosmicElement::new(
Tabs::new(
windows.iter().enumerate().map(|(i, w)| {
let user_data = w.user_data();
user_data.insert_if_missing(Id::unique);
Tab::new(
w.title(),
w.app_id(),
user_data.get::<Id>().unwrap().clone(),
)
.on_press(Message::PotentialTabDragStart(i))
.on_right_click(Message::TabMenu(i))
.on_close(Message::Close(i))
self.tab_models.iter().enumerate().map(|(i, tab)| {
Tab::new(tab)
.on_press(Message::PotentialTabDragStart(i))
.on_right_click(Message::TabMenu(i))
.on_close(Message::Close(i))
}),
active,
windows[active].is_activated(false),
activated,
group_focused,
)
.id(SCROLLABLE_ID.clone())
Expand All @@ -1068,7 +1147,7 @@ impl Program for CosmicStackInternal {
.into(),
];

let radius = if windows[active].is_maximized(false) {
let radius = if maximized {
Radius::from(0.0)
} else {
Radius::from([8.0, 8.0, 0.0, 0.0])
Expand Down Expand Up @@ -1255,6 +1334,8 @@ impl SpaceElement for CosmicStack {
SpaceElement::refresh(w)
});
});

self.0.queue_message(Message::Refresh);
SpaceElement::refresh(&self.0);
}
}
Expand Down
Loading
Loading